天天看点

Spring IOC 容器注解使用

1、Spring 容器继承图

Spring IOC 容器注解使用

 2、控制反转和依赖注入

  1. 什么是控制反转?我觉得有必要先了解软件设计的一个重要思想:依赖倒置原则(Dependency Inversion Principle )
  2. 什么是依赖倒置原则?假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
Spring IOC 容器注解使用

从上图来看好像没什么问题,但万一轮胎尺寸改了,那么地盘需要改,地盘改了,车身也改了,让后整个汽车构造都改了,牵一发而动全身。但如果反过来假如汽车公司决定修改轮胎的 我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车 的设计了。

Spring IOC 容器注解使用

IOC容器的核心思想在于资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第 一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。举个例子,假如我们需要找个房子,我们需要租个房子,我们可以自己去找房源(但我们需要依赖房源等因素),也可以交由中介进行管理(这样子我们就是不直接依赖房源了,这个交给中介管理)。

Spring IOC 容器注解使用

3、Spring IOC 容器注解使用

1、我们很多时候通过基于xml的形式定义Bean的信息。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.cym.bean.compent.User"></bean>
</beans>
           

读取容器中的bean的方式

public class BeanMain {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
        Object user = classPathXmlApplicationContext.getBean("user");
        System.out.println(user);
//        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
//        Object user1 = annotationConfigApplicationContext.getBean("person");
//        System.out.println(user1);
    }
}
           

2、基于读取配置类的形式定义Bean信息 

@Configuration
public class BeanConfig {

    @Bean(name = "person")
    public User user(){
        return new User();
    }
}
           

注意 : 通过 @Bean 的形式是使用的话, bean 的默认名称是方法名,若 @Bean(value="bean 的名称 ") 那么 bean 的名称是指定的 去容器中读取 Bean 的信息( 传入配置类 )

public class BeanMain {

    public static void main(String[] args) {
//        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
//        Object user = classPathXmlApplicationContext.getBean("user");
//        System.out.println(user);
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("person");
        System.out.println(user1);
    }
}
           

3、在配置类上写 @CompentScan 注解来进行包扫描

@Configuration
@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
//                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {

}
           

从容器中获取bean

public class UserMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acx = new AnnotationConfigApplicationContext(UserConfig.class);
        String[] beans = acx.getBeanDefinitionNames();
        for (String bean :beans){
            System.out.println("bean定义的信息:"+bean);
        }
    }
}
           
Spring IOC 容器注解使用

排除用法 excludeFilters( 排除 @Controller 注解的 , 和UserService的bean )

@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
@ComponentScan(basePackages = {"com.cym.componentscan"},
        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {

}
           
Spring IOC 容器注解使用

包含用法 includeFilters , 注意,若使用包含的用法, 需要把 useDefaultFilters 属性设置为 false ( true 表 示扫描全部的)

@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
//                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
@ComponentScan(basePackages = {"com.cym.componentscan"},
        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {

}
           
Spring IOC 容器注解使用

@ComponentScan.Filter type 的类型

注解形式的 FilterType.ANNOTATION @Controller @Service @Repository @Compent
指定类型的 FilterType.ASSIGNABLE_TYPE
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
           
aspectj 类型的 FilterType.ASPECTJ( 不常用 )
正则表达式的 FilterType.REGEX(不常用 )
自定义的 FilterType.CUSTOM
package org.springframework.context.annotation;

public enum FilterType {
    //注解形式 比如@Controller @Service @Repository @Compent
    ANNOTATION,

    //指定的类型
    ASSIGNABLE_TYPE,
    
    //aspectJ形式的
    ASPECTJ,
    
    //正则表达式的
    REGEX,

    //自定义的
    CUSTOM;

    private FilterType() {
    }
}
           

FilterType.CUSTOM 自定义类型如何使用

public class MyFilterType implements TypeFilter {

    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类的注解源信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前类的class的源信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类的资源信息
        Resource resource = metadataReader.getResource();
        //扫描UserController、UserService
        if (classMetadata.getClassName().contains("UserController") || classMetadata.getClassName().contains("UserService")) {
            return true;
        }
        return false;
    }
}
           

配置类

@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
//                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
@ComponentScan(basePackages = {"com.cym.componentscan"},
        includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, value = MyFilterType.class)}, useDefaultFilters = false)
public class UserConfig {

}
           

4、配置 Bean 的作用域对象   在不指定 @Scope 的情况下,所有的 bean 都是单实例的 bean, 而且是饿汉加载 ( 容器启动实例就创建 好了 )

@Configuration
public class BeanConfig {

    @Bean
    public User user(){
        return new User();
    }
}
           
@Setter
@Getter
public class User {

    public User() {
        System.out.println("在不指定@Scope的情况下,所有的bean都是单实例的bean,而且是饿汉加载(容器启动实例就创建好了)");
    }

    private String userName;

    private String age;

    private String gender;

    private String phone;

    private String address;

    private Date birthday;

}
           

获取容器中的bean

Spring IOC 容器注解使用

指定 @Scope 为 prototype 表示为多实例的,而且还是懒汉模式加载( IOC 容器启动的时候,并不会创建对象,而是 在第一次使用的时候才会创建)

@Configuration
public class BeanConfig {

    @Bean
    @Scope(value = "prototype")
    public User user(){
        return new User();
    }
}
           
Spring IOC 容器注解使用

@Scope 指定的作用域方法取值  

singleton 单实例的 ( 默认 )
prototype 多实例的
request 同一次请求
session 同一个会话级别

5、Bean的懒加载@Lazy(主要针对单实例的bean 容器启动的时候,不创建对象,在第一次使用的时候才会创建该对象)

@Configuration
public class BeanConfig {

    @Bean
    @Lazy
    public User user(){
        return new User();
    }
}
           

6、@Conditional进行条件判断等(可以用于自动装配)

场景,有二个组件Parents 和Childrents ,我的Childrents组件是依赖于Parents的组件

应用: 自己创建一个My Condition 的类 实现 Condition接口

public class MyCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //判断容器中是否有Parent的组件
        if (conditionContext.getBeanFactory().containsBean("parents")){
            return true;
        }
        return false;
    }
}
           

配置类

@Configuration
public class BeanConfig {

    @Bean
    public Parents parents() {
        return new Parents();
    }

    @Bean
    @Conditional(value = MyCondition.class)
    public Childrens childrens() {
        return new Childrens();
    }
}
           

7、往IOC 容器中添加组件的方式

通过 @CompentScan [email protected] @Service @Respository @compent,适用场景 : 针对我们自己写的组件可以通过该方式来进行加载到容器中。
通过 @Bean 的方式来导入组件 ( 适用于导入第三方组件的类 )
通过 @Import 来导入组件 (导入组件的 id为全类名路径),通过 @Import 的 ImportSeletor 类实现组件的导入 ( 导入组件的 id 为全类名路径 )
通过实现 FacotryBean 接口来实现注册 组件

通过@Import 的ImportSeletor类实现组件的导入 (导入组件的id为全类名路径,如:com.cym.Import.compent.Parents)

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.cym.Import.compent.Dog"};
    }
}
           

 通过@Import的 ImportBeanDefinitionRegister导入组件 (可以指定bean的名称)

public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,  BeanDefinitionRegistry registry) {
        //创建一个bean定义对象
        RootBeanDefinition parents = new RootBeanDefinition(Parents.class);
        RootBeanDefinition childrens = new RootBeanDefinition(Childrens.class);
        RootBeanDefinition dog = new RootBeanDefinition(Dog.class);
        //把bean定义对象导入到容器中
        registry.registerBeanDefinition("parents",parents);
        registry.registerBeanDefinition("childrens",childrens);
        registry.registerBeanDefinition("dog",dog);
    }
}
           

 配置类

@Configuration
//@Import({Parents.class,Childrens.class, MyImportSelector.class})
@Import(MyBeanDefinitionRegister.class)
public class BeanConfig {

}
           
Spring IOC 容器注解使用

通过实现 FacotryBean 接口来实现注册 组件

public class MyrFactoryBean implements FactoryBean<User> {
    //返回bean对象
    @Override
    public User getObject() throws Exception {
        return new User();
    }
    //返回bean的类型
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    //是否为单例
    @Override
    public boolean isSingleton() {
        return false;
    }
           

配置类

@Configuration
public class BeanConfig {
    
    //注册我们自己factoryBean
    @Bean
    public MyrFactoryBean myrFactoryBean(){
        return new MyrFactoryBean();
    }
}
           
Spring IOC 容器注解使用

7、Bean的初始化方法和销毁方法

  • 什么是bean的生命周期?bean的创建----->初始化----->销毁方法由容器管理Bean的生命周期,我们可以通过自己指定bean的初始化方法和bean的销毁方法。
public class User {

    public User() {
        System.out.println("我是构造器");
    }

    public void init(){
        System.out.println("我是bean的生命周期的初始化方法init");
    }

    public void destroy(){
        System.out.println("我是bean的生命周期的销毁方法destroy");
    }
}


@Configuration
public class BeanConfig {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public User user() {
        return new User();
    }
}


public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("user");
        System.out.println(user1);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用

针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法。针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受 IOC容器的管理  

  • 通过 InitializingBean和DisposableBean 的二个接口实现bean的初始化以及销毁方法
/**
 * @description:
 * @author: 通过 InitializingBean和DisposableBean 的二个接口实现bean的初始化以及销毁方法
 * @date: 2020-04-12 12:27
 */
public class Person implements InitializingBean, DisposableBean {

    public Person() {
        System.out.println("我是Person构造器");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean的destroy()方法 ");
    }

    /**
     * 集成了InitializingBean这个类的在bean创建完成后会自动调用该该方法
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean的 afterPropertiesSet方法");
    }
}



@Configuration
public class BeanConfig {


    @Bean
    public Person person(){
        return new Person();
    }
}


/**
 * @description: 基于读取配置类的形式定义Bean信息
 * @author: 
 * @date: 2020-04-11 19:45
 */
public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("person");
        System.out.println(user1);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用
  • 通过JSR250规范 提供的注解@PostConstruct 和@ProDestory标注的方法
@Component
public class Dog {

    public Dog() {
        System.out.println("Dog构造方法");
    }
    @PostConstruct
    public void init() {
        System.out.println("Dog 的PostConstruct标志的方法");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("Dog 的PreDestory标注的方法");
    }
}

@Configuration
@ComponentScan(basePackages = {"com.cym.init_destroy"})
public class BeanConfig {

}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object dog = annotationConfigApplicationContext.getBean("dog");
        System.out.println(dog);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用
  • 通过Spring的BeanPostProcessor的 bean的后置处理器会拦截所有bean创建过程(postProcessBeforeInitialization在init方法之前调用 postProcessAfterInitialization 在 init 方法之后调用)
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor...postProcessBeforeInitialization:"+beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor...postProcessAfterInitialization:"+beanName);
        return bean;
    }
}


@Configuration
@ComponentScan(basePackages = {"com.cym.init_destroy"})
public class BeanConfig {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public User user() {
        return new User();
    }

}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
//        Object dog = annotationConfigApplicationContext.getBean("cat");
//        System.out.println(dog);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用

8、通过@Value [email protected]来给组件赋值

@Setter
@Getter
public class User {

    @Value("张三")//通过普通的方式
    private String userName;
    @Value("#{28-3}")//spel方式来赋值
    private String age;
    @Value("${user.gender}")//通过读取外部配置文件的值
    private String gender;
    @Value("${user.phone}")//通过读取外部配置文件的值
    private String phone;
    @Value("${user.address}")//通过读取外部配置文件的值
    private String address;
    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", age='" + age + '\'' +
                ", gender='" + gender + '\'' +
                ", phone='" + phone + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

@Configuration
@PropertySource(value = {"classpath:user.properties"})//指定外部文件的位置
public class BeanConfig {

    @Bean
    public User user(){
        return new User();
    }
}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("user");
        System.out.println(user1);
    }
}
           
Spring IOC 容器注解使用

9、自动装配@AutoWired的使用

@Component
public class UserDao {

    public void printUserName(String userName){
        System.out.println("my name is " + userName);
    }
}

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public void printName(String userName){
        userDao.printUserName(userName);
    }
}

@Configuration
@ComponentScan(basePackages = {"com.cym.value"})
public class BeanConfig {

    @Bean
    public User user(){
        return new User();
    }
}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        UserService userService = (UserService) annotationConfigApplicationContext.getBean("userService");
        userService.printName("张三");
    }
}
           
Spring IOC 容器注解使用
  • 自动装配首先时按照类型进行装配,若在IOC容器中发现了多个相同类型的组件,那么就按照 属性名称来进行装配比如,我容器中有二个UserDao类型的组件 一个叫UserDao 一个叫UserDao2那么我们通过@AutoWired 来修饰的属性名称时UserDao,那么拿就加载容器的UserDao组件,若属性名称为UserDao2 那么他就加载的时UserDao2组件。
  • 假设我们需要指定特定的组件来进行装配,我们可以通过使用@Qualifier("UserDao")来指定装配的组件或者在配置类上的@Bean加上@Primary注解
@Autowired 
@Qualifier("userDao") 
private UserDao userDao2;
           
  • 假设我们容器中即没有userDao 和userDao2,那么在装配的时候就会抛出异常若我们想不抛异常 ,我们需要指定 required为false的时候可以了
    @Autowired(required = false)
     @Qualifier("userDao")
     private UsergDao userDao2;
               

10、@Resource(JSR250规范)

  • 功能和@AutoWired的功能差不多一样,但是不支持@Primary 和@Qualifier的支持

11、@InJect(JSR330规范)

  • 需要导入jar包依赖功能和支持@Primary功能 ,但是没有Require=false的功能
<dependency> 
    <groupId>javax.inject</groupId> 
    <artifactId>javax.inject</artifactId> 
    <version>1</version>
 </dependency>
           

12、通过@Profile注解 来根据环境来激活标识不同的Bean

  • @Profile标识在类上,那么只有当前环境匹配,整个配置类才会生效,@Profile标识在Bean上 ,那么只有当前环境的Bean才会被激活没有标志为@Profile的bean 不管在什么环境都可以被激活。
@Configuration
@PropertySource(value = {"classpath:ds.properties"})
public class MainConfig implements EmbeddedValueResolverAware {
    @Value("${ds.username}")
    private String userName;
    @Value("${ds.password}")
    private String password;
    private String jdbcUrl;
    private String classDriver;

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.jdbcUrl = resolver.resolveStringValue("${ds.jdbcUrl}");
        this.classDriver = resolver.resolveStringValue("${ds.classDriver}");
    }//标识为测试环境才会被装配 

    @Bean
    @Profile(value = "test")
    public DataSource testDs() {
        return buliderDataSource(new DruidDataSource());
    }

    //标识开发环境才会被激活 
    @Bean
    @Profile(value = "dev")
    public DataSource devDs() {
        return buliderDataSource(new DruidDataSource());
    }

    //标识生产环境才会被激活
    @Bean
    @Profile(value = "prod")
    public DataSource prodDs() {
        return buliderDataSource(new DruidDataSource());
    }

    private DataSource buliderDataSource(DruidDataSource dataSource) {
        dataSource.setUserName(userName);
        dataSource.setPassword(password);
        dataSource.setClassDriver(classDriver);
        dataSource.setJdbcUrl(jdbcUrl);
        return dataSource;
    }
}
           

激活切换环境的方法 方法一 : 通过运行时 jvm 参数来切换 -Dspring.profiles.active=test|dev|prod 方法二 : 通过代码的方式来激活

public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("test", "dev");
        ctx.register(MainConfig.class);
        ctx.refresh();
        Object testDs = ctx.getBean("testDs");
        System.out.println(testDs);
        Object devDs = ctx.getBean("devDs");
        System.out.println(devDs);
//        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
//        for (String item : beanDefinitionNames){
//            System.out.println(item);
//        }
    }
}
           
Spring IOC 容器注解使用