当前位置: 首页 > news >正文

SpringBoot学习day6

文章目录

  • spring中注入bean的几种方式
  • bean的注入控制
  • bean的依赖配置
  • 自动配置原理

spring中注入bean的几种方式

  1. 通过xml来进行注入,首先我们需要通过创建一个配置文件applicationContext.xml,然后在这个配置文件中通过<bean></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">
        <!--id是对应的bean的名字,class就是bean对象对应的全路径
            如果没有写id,那么对应的id默认就是它的全路径+#下标
        -->
        <bean id="cat" class="com.demo.domain.Cat"></bean>
        <bean class="com.demo.domain.Dog"></bean>
        <bean class="com.demo.domain.Dog"></bean>
    </beans>
    

    至此我们已经实现了注入bean,如果我们要获取bean时候,我们需要通过读取spring的核心配置文件,然后通过ApplicationContext对象调用getBean方法来获取对应的bean对象,其中getBean方法可以有几种方式:

    • getBean(对应的Bean的字节码对象):如果通过这一种方式的话,那么就会返回对应的Bean对象,但是这时候如果在Spring容器中有多个这种类型的bean对象的时候,那么调用这个方法的时候就会发生报错,提示这种bean不唯一的错误,因为没有办法知道要拿的是哪个bean。
    • getBean(String beanName):通过这一种方式,就会获取对应的beanName名称的bean对象,但是因为没有明确bean是什么类型,所以最后返回的对象是Object对象,所以这时候我们需要进行类型转换。
    • getBean(String beanName,Class clazz):通过这种方式,就会获取对应的beanName名称的bean对象,并且应为有传递了这个bean对象的字节码对象,所以可以明确知道这个bean是什么类型,所以最后返回的对象就是我们需要的bean类型,因此不再需要类型转换了。
    public class App1 {
        public static void main(String[] args) {
            ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
            String[] names = application.getBeanDefinitionNames();//获取定义的bean的名字
            for(String name : names){
                System.out.println(name);
            }
        }
    }
    

    对应的输出为:

    cat
    com.demo.domain.Dog#0
    com.demo.domain.Dog#1
    

    如果需要注入第三方技术的bean的时候,通过xml进行注入的时候,也是这样的道理,只是需要我们导入对应的依赖之后才可以。

  2. 通过注解来注入bean对象:

    如果需要通过注解来注入bean对象的时候,我们可以利用一下几个注解:@Component , @Controller, @Service

    , @Repository,只是@Component可以在任意层中添加到bean容器中,@Controller则是将controller层中的类添加到bean容器中,@Service,@Repository则是将service层,dao层中的类添加到bean容器中。

    只是在使用这些注解的同时,我们还需要在配置文件中利用context命名空间进行组件扫描,只有添加这个语句,才可以将利用上面注解修饰的类添加到bean容器中,否则那些被上面注解修饰的类是没有办法添加到spring容器中的。

    对应的applicationContext文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--利用context命名空间进行组件扫描,扫描domain包以及子包,以及config包以及子包-->
        <context:component-scan base-package="com.demo.domain,com.demo.config"></context:component-scan>
    </beans>
    
    @Component("杰瑞") //将cat这个类添加到spring容器中,bean的名称为为杰瑞,如果没有设置,那么就是类名,并且第一个字母小写
    public class Cat {
    }
    
    @Component
    public class Dog {
    }
    
    

    利用applicationContext调用getBeanDefinitionNames的时候,就可以获取所有的bean对象的名称,对应的代码为:

    public class App1 {
        public static void main(String[] args) {
            ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
            String[] names = application.getBeanDefinitionNames();//获取定义的bean的名字
            for(String name : names){
                System.out.println(name);
            }
        }
    }
    

    输出为:

    杰瑞
    dog
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    

    但是这时候我们如果将第三方技术注入到spring容器中呢?这时候我们需要利用注解@Bean,这样他就可以将方法的返回值添加到spring容器中了。但是这个方法所在的类必须也是在spring容器中,否则如果这个类是一个普通类,那么即使方法中添加了注解@Bean,spring也不会扫描这个普通类,也就不会进入到这个类的内部了。这时候我们可以利用注解@Configuration来说明这个类是一个配置类,而在@Configuration注解中,也有利用到了注解@Component,那么这时候利用注解@Configuration,也可以将类添加到spring容器中了。

    对应的代码为:

    //这里使用@Component也是可以的
    @Configuration
    public class DbConfig {
        @Bean
        public DataSource dataSource(){
            System.out.println("dataSource is init....");
            return new ComboPooledDataSource();
        }
    }
    

    这时候对应的输出结果为:

    dataSource is init....
    八月 30, 2022 4:43:56 下午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    八月 30, 2022 4:43:57 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
    杰瑞
    dog
    dbConfig
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    dataSource
    

    但是这时候既然@Component和@Configuration都可以将配置类添加到spring容器中,那么为什么还需要使用注解@Configuration,换句话说,为什么还需要@Configuration呢?

    这时候我们的测试代码为:

    public class App1 {
        public static void main(String[] args) {
            ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
            DbConfig dbConfig = application.getBean("dbConfig", DbConfig.class);
            
            System.out.println(dbConfig.dog2());
            System.out.println(dbConfig.dog2());
            System.out.println(dbConfig.dog2());
    
        }
    }
    
    

    如果在DbConfig这个类上面使用的是注解@Configuration,那么对应的结果为:

    dataSource is init....
    八月 30, 2022 5:15:00 下午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    八月 30, 2022 5:15:00 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
    dog2 is init......
    com.demo.domain.Dog@35e2d654
    com.demo.domain.Dog@35e2d654
    com.demo.domain.Dog@35e2d654
    

    如果在DbConfig类上面使用的是注解@Component,那么对应的结果为:

    dataSource is init....
    八月 30, 2022 5:12:55 下午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    八月 30, 2022 5:12:56 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
    dog2 is init......
    dog2 is init......
    com.demo.domain.Dog@51e5fc98
    dog2 is init......
    com.demo.domain.Dog@7c469c48
    dog2 is init......
    com.demo.domain.Dog@12e61fe6
    

    为什么有这样的区别呢?这是因为注解@Configuration中存在一个属性proxyMethodBean,默认值为true,这样他会将我们生成一个cglib代理对象,也即第一次调用对应的方法(这个方法需要被@Bean修饰)的时候,他会将返回值添加到spring容器中,然后下次再调用的时候,那么就会直接从spring容器中获取,而不会再次进入到这个方法内部了,如果proxyMethodBean的值为false,那么这时候它的结果和@Component的结果一样,每次调用方法的时候,都需要进入到方法内部执行。而@Component则是每次调用方法的时候,都会进入到方法内部,所以这时候得到的对象是不一样的。可以参考这个文章:@Component和@Configuration

  3. 使用注解之后,发现还需要applicationContext.xml这个配置文件中,利用context命名空间进行组件扫描,那么能不能指利用注解,而不再需要这个配置文件呢?答案是肯定的,我们需要利用配置类,然后再配置类的上方使用注解@ComponentScan来进行组件扫描即可。这时候因为没有了配置文件,所以在获取ApplicationContext对象的时候,我们不可以在通过ClassPathXmlApplicationContext来获取了,而是通过AnnotationConfigApplicationContext来获取,对应的代码为:

    @Configuration
    @ComponentScan(basePackages = {"com.demo.domain","com.demo.config"})
    public class SpringConfig {
        @Bean
        public DogFactoryBean factoryBean(){
            return new DogFactoryBean();
        }
    
        @Bean
        public Cat cat(){
            System.out.println("Cat is init..........");
            return new Cat();
        }
    }
    
    

    测试代码为:

    public class App3 {
        public static void main(String[] args) {
            ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
            /*
            在注解@Configuration中,有设置属性proxyMethodBean,默认值为true,意思是
            当在配置类中创造bean对象的时候,方法返回的是一个新建的新建的对象,这时候
            如果它的值为true,那么第一次调用这个方法的时候,将它的返回值添加到spring容器
            中,当下次在调用这个方法的时候,不再进入方法内部执行,而是直接从spring容器中取出的,否则
            如果它的值为false,那么每次都需要进入到方法内部执行代码,然后返回的对象都是新建的,
            所以每次调用得到的对象都是不同的
             */
            SpringConfig springConfig = app.getBean("springConfig", SpringConfig.class);
            System.out.println(springConfig.cat());
            System.out.println(springConfig.cat());
            System.out.println(springConfig.cat());
        }
    }
    
    

    对应的结果为:

    dataSource is init....
    八月 30, 2022 5:25:57 下午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    八月 30, 2022 5:25:58 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
    Cat is init..........
    com.demo.domain.Cat@62150f9e
    com.demo.domain.Cat@62150f9e
    com.demo.domain.Cat@62150f9e
    
  4. 手动注册bean对象:当我们获取到了AnnotationConfigApplicationContext对象之后,我们通过调用register或者registerBean方法来注册bean对象:

    • register(Class clazz): 注册对应的字节码对象为bean对象,这时候bean对象的名字就是它的类名,并且第一个字母为小写。这时候对应的类可以不使用注解来将其添加到bean也可以,因为register会将其注册的,但是如果这个类已经使用对应的注解,将其添加到spring容器中,那么调用这个方法的时候,并没有生效的。
    • registerBean(String beanName,Class clazz,):注册对应的bean对象,并且bean对象的名字是beanName
    • registerBean(String beanName,Class clazz,constructionArags):注册对应的bean对象,并且bean对象的名字为beanName,并且有传递可变参数constructionArags,这时候,同一个beanName中多次调用这个方法,并且传递的参数不一样,那么将会以最后一个参数的为主,因为之前的已经被覆盖了。
    @Configuration
    @ComponentScan({"com.demo.domain"})
    public class SpringConfig2 {
    }
    

    对应的实体类代码:

    @Component("杰瑞")
    public class Cat {
    }
    
    @Component
    public class Dog {
        public Dog() {
        }
        int age;
    
        public Dog(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Dog{" +
                    "age=" + age +
                    '}';
        }
    }
    

    测试代码:

    public class App4 {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig2.class);
            //手动注册bean对象
            app.register(Cat.class);//注册Cat类型的bean对象,并且bean的名字为全路径
            app.registerBean("yellow", Dog.class,1);
            app.registerBean("yellow",Dog.class,2);
            app.registerBean("yellow",Dog.class,3);
            String[] names = app.getBeanDefinitionNames();
            for(String name : names){
                System.out.println(name);
            }
            System.out.println(app.getBean("yellow",Dog.class));
        }
    }
    
    

    输出结果为:

    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    springConfig2
    杰瑞
    dog
    yellow
    Dog{age=3}
    
  5. 定义一个类,使得这个类实现了ImportSelector接口,然后重写它的方法selectImport,方法的返回值就是要添加的bean对象。然后需要在核心配置类中利用@Import注解来引入这个类,然后就可以进行测试了,对应的代码为:

    public class MyImportSelector implements ImportSelector {
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            /*
            将字符串数组中的元素添加到spring容器中,对应的元素就是对应的类的全路径,
            这时候获取到的bean对象的名字就是它的全路径。但是如果这个类上面使用了
            @Component等注解,并且设置它的名字,那么这时候对应的bean对象的名字就是自己设置的
            否则,如果没有设置它的名字,或者根本没有使用@Component等注解,那么对应的bean对象的名字
            就是它的全路径
            */
            return new String[]{"com.demo.domain.Dog","com.demo.domain.Cat"};
        }
    }
    

    实体类:

    @Component("杰瑞")
    public class Cat {
    }
    
    public class Dog {
        public Dog() {
        }
        int age;
    
        public Dog(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Dog{" +
                    "age=" + age +
                    '}';
        }
    }
    
    

    核心配置类:

    @Configuration
    @Import(MyImportSelector.class) //利用注解@Import,这样就可以导入对应的类
    public class SpringConfig3 {
    }
    

    测试代码:

    public class App5 {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig3.class);
            String[] names = app.getBeanDefinitionNames();
            for(String name : names){
                System.out.println(name);
            }
        }
    

    测试结果:

    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    springConfig3
    com.demo.domain.Dog
    杰瑞
    
  6. 定义一个类,让这个类实现接口ImportBeanDefinitionRegistrar,重写它的对应方法,从而注册bean

    对应的代码为:

    public class MyRegistrar implements ImportBeanDefinitionRegistrar {
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
            registry.registerBeanDefinition("Tom猫",beanDefinition);
        }
    }
    

    配置类代码:

    @Configuration
    @Import(MyRegistrar.class)
    public class SpringConfig4 {
    }
    

    测试类代码:

    public class App6 {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig4.class);
            String[] names = app.getBeanDefinitionNames();
            for(String name : names){
                System.out.println(name);
            }
        }
    }
    
    

    运行结果为:

    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    springConfig4
    Tom猫
    
  7. 定义一个类,让这个类实现接口BeanDefinitionRegistryPostProcessor接口,然后重写方法,从而将对应的bean注册。值得注意的是,因为是BeanDefinitionRegistryPostProcessor接口,根据它的名称可以知道,它是在bean定义之后,然后再处理的,所以如果这个bean之前已经存在了,那么再这个接口中重新注入相同名字的bean的时候,就会覆盖之前的。对应的代码为:

    public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            //注册bean,只是他是在bean定义之后才进行的,
            BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
            registry.registerBeanDefinition("bookService",beanDefinition);
    
        }
    
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    
        }
    }
    

    同时为了证明这种方式将会覆盖之前的bean,所以需要再次通过实现接口BeanDefinitionRegistrar来注入bean:

    public class MyRegistrar1 implements ImportBeanDefinitionRegistrar {
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
            registry.registerBeanDefinition("bookService",beanDefinition);
        }
    }
    
    
    public class MyRegistrar2 implements ImportBeanDefinitionRegistrar {
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();
            registry.registerBeanDefinition("bookService",beanDefinition);
        }
    }
    

    对应的实体类:

    public interface BookService {
        void check();
    }
    
    @Service("bookService")
    public class BookServiceImpl1 implements BookService {
        public void check() {
            System.out.println("bookService 1 .. ");
        }
    }
    
    
    public class BookServiceImpl2 implements BookService {
        public void check() {
            System.out.println("bookService 2.... ");
        }
    }
    
    
    public class BookServiceImpl3 implements BookService {
        public void check() {
            System.out.println("bookService 3 ....... ");
        }
    }
    
    
    public class BookServiceImpl4 implements BookService {
        public void check() {
            System.out.println("bookService 4......... ");
        }
    }
    
    

    对应的配置类:

    @Configuration
    @Import({BookServiceImpl1.class, MyRegistrar2.class,MyRegistrar1.class, MyPostProcessor.class})
    public class SpringConfig5 {
    }
    

    测试类的代码:

    public class App7 {
        public static void main(String[] args) {
            ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig5.class);
            BookService bookService = app.getBean("bookService", BookService.class);
            bookService.check();
        }
    }
    

    运行结果是根据上面配置类中注解@Import的值来设置的:

    • @Import({BookServiceImpl1.class}):那么输出的是bookService 1…
    • @Import({BookServiceImpl1.class,MyRegistar1.class})或者@Import({MyRegistar1.class,BookServiceImpl1.class}),都是输出bookService 2…,这说明了优先级是通过实现接口BeanDefinitionRegistrar注入bean这种方式的优先级更加高,如果是@Import({BookServiceImpl1.class,MyRegistar1.class,MyRegistar2.class}),那么输出的是bookService 3…,那么说明同一优先级的时候,那么是根据@Import中的顺序来配置的
    • @Import({BookServiceImpl1.class,MyRegistar1.class,MyRegistar1.class,MyPostProcessor.class})还是@Import({MyPostProcessor.class,BookServiceImpl1.class,MyRegistar1.class,MyRegistar1.class}),都是输出的是bookService 4…,说明通过实现接口BeanDefinitionRegistryPostProcessor来注入bean的优先级最高.
  8. 在配置类的上方使用注解@Import,然后属性classes的值是我们需要导入的字节码对象,这样就可以将对应的字节码对象添加到bean中,这样就不需要在对应的类上方使用@Component等注解来注入bean了,从而降低了解耦。如果导入的是一个配置类,那么在将这个配置类导入bean的同时,这个配置类内部中使用@Bean注解修饰的方法也会注入到bean中。

bean的注入控制

如果我们需要控制bean的注入,那么上面的几种注入bean的方式中,只有以下方式可以实现bean的注入控制:

  • 通过实现ImportSelector接口,然后在配置类中利用注解@Import来导入实现类。
  • 通过实现BeanDefinitionRegistrar接口,然后在配置类中利用注解@Import来导入对应的实现类
  • 通过实现BeanDefinitionRegistrarPostProcessor接口,然后在配置类中利用注解@Import来导入对应的实现类。
  • 通过AnnotationConfigApplicationContext对象调用register方法来注册bean的时候,可以进行bean的注入控制。

所以可以拿第一种方式为例,如果能够在项目中找到mysql驱动,那么就可以注入druid数据源为bean,否则不可以注入,而判断这个类是否存在于项目中,则可以通过class.forName(xxxx)来寻找,如果返回值为null,说明不存在,否则存在.对应的代码为:

public class MyImportSelector2 implements ImportSelector {
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        try {
           //如果能够找到数据库驱动,那么就需要注入数据源为bean,否则不需要
            Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
            if(clazz != null){
                return new String[]{"com.alibaba.druid.pool.DruidDataSource"};
            }
        } catch (ClassNotFoundException e) {
            //如果clazz没有找到,那么就会发生报错,此时直接返回,不需要注入数据源
            return new String[0];
        }
        return null;
    }
}

配置类代码为:

@Configuration
@Import(MyImportSelector2.class)
public class SpringConfig6 {
}

测试类:

public class App8 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig6.class);
        String[] names = app.getBeanDefinitionNames();
        for(String name : names){
            System.out.println(name);
        }
    }
}

如果我们只导入了druid的依赖,而没有导入mysql-connector-java的依赖,那么这时候运行结果为:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig6

并没有注入了数据源,否则,如果我们在导入druid以来的同时,也导入了mysql-connector-java依赖,那么这时候运行结果为:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig6
com.alibaba.druid.pool.DruidDataSource

但是显然,上面方式实现bean的注入控制显然有限繁琐,所以这时候我们需要通过注解来实现bean的注入控制,主要是通过注解@Conditional,但是这个注解的代码为:

public @interface Conditional {
    Class<? extends Condition>[] value();
}

也即它的属性值要求是继承了Condition的类,而Condition这个接口的代码为:

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

所以通过自定义类实现了Condition接口,然后重写方法matches,那么就可以控制注入bean,如果重写的方法中返回的是false,那么不可以注入,否则可以注入。因此我们自定义一个类MyCondition,重写matches方法从而控制druid数据源的注入.

public class MyCondition implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
       //如果返回的是false,那么使用注解@Conditional(MyConditional.class)
        //修饰的类或者方法,不会注入到spring容器中,否则返回是true的时候,就可以注入
        try {
            Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
            if(clazz != null){
                return true;
            }
        } catch (ClassNotFoundException e) {
        }
        return false;
    }
}

对应的配置类为:

@Configuration
public class SpringConfig7 {
    @Bean
    @Conditional(MyCondition.class)
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }
}

这时候测试的时候,如果我们已经导入了mysql-connector-java依赖,那么就可以将数据源注入,否则不可以。

但是通过实现Condition接口来控制bean的注入依旧很麻烦,所以这时候可以使用注解@ConditionalOnxxx来控制bean的注入。但是在使用@ConditionalOnxxxx这个注解的时候,首先我们需要先导入spring-boot-starter依赖,然后就饿可以使用这些注解了。所以上面的配置类的代码可以修改为:

@Configuration
public class SpringConfig7 {
    @Bean
    //如果能够找到com.mysql.cj.jdbc.Driver这个类,那么就可以注入这个bean,否则不可以
    @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }
}

bean的依赖配置

如果我们需要绑定bean的属性的时候,可以使用注解@ConfigurationProperties,从而可以使得这个bean绑定到了配置文件中的某一个属性了。但是使用@ConfigurationProperties注解的前提时,这个类是一个bean对象,否则就会提示错误。并且这个类属性要含有get/set方法才可以,并且类的属性成员名要和配置文件中的属性名相同,否则是没有办法进行属性绑定的

对应的代码如下所示:

public class Cat {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


public class Mouse {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Mouse{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

@Component
@ConfigurationProperties(prefix = "cartoon")
public class CartoonCatAndMouse {
    private Cat cat;
    private Mouse mouse;

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Mouse getMouse() {
        return mouse;
    }

    public void setMouse(Mouse mouse) {
        this.mouse = mouse;
    }

    public void play(){
        System.out.println("cat: " + cat.getName() + ", cat.age = " + cat.getAge() +
                         " mouse: name = " + mouse.getName() + ", age =  "+ mouse.getAge());
    }
}


对应的配置文件为:

cartoon:
  cat:
    name: tom
    age: 5
  mouse:
    name: jerry
    age: 3

这时候的CartoonCatAndMouse中的属性成员就可以绑定到了配置文件中的cartoon的属性了。但是这时候会存在一个问题,如果我们的配置文件中并没有存在cat这个属性或者不存在cartoon属性,那么这时候CartoonCatAndMouse就没有办法绑定到了配置文件中的属性,从而导致cat,mouse是null,这时候调用方法play的时候,就会发生空指针异常。这是我们并不想看到的结果,所以这时候我们需要再定义一个类CartoonCatAndMousePropertites,在这个类中同样存在属性cat,mouse,并且将这个类和配置文件中的属性cartoon绑定,这样CartoonCatAndMouse就可以不需要和配置文件中进行绑定了,如果需要将CartoonCatAndMouse中cat,mouse和配置文件中的属性绑定的时候,那么这时候我们只需要注入CartoonCatAndMouseProperties,通过CartoonCatAndMouseProperties调用对应的方法来给cat,mouse赋值即可。

对应的代码为:

@Component
@ConfigurationProperties(prefix = "cartoon")
public class CartoonCatAndMouseProperties {
    private Cat cat;
    private Mouse mouse;

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Mouse getMouse() {
        return mouse;
    }

    public void setMouse(Mouse mouse) {
        this.mouse = mouse;
    }
}

对应的CartoonCatAndMouse代码为:

@Component
public class CartoonCatAndMouse {
    private Cat cat;
    private Mouse mouse;

    private CartoonCatAndMouseProperties properties;

    public CartoonCatAndMouse(CartoonCatAndMouseProperties properties){
        this.properties = properties;
        cat = new Cat();
        cat.setName(properties.getCat() != null
                && !StringUtils.isEmpty(properties.getCat().getName()) ? properties.getCat().getName() : "tt");
        cat.setAge(properties.getCat() != null && properties.getCat().getAge() != null ? properties.getCat().getAge() : 4);
        mouse = new Mouse();
        mouse.setName(properties.getMouse() != null
                && !StringUtils.isEmpty(properties.getMouse().getName()) ? properties.getMouse().getName() : "mouseTT");
        mouse.setAge(properties.getMouse() != null && properties.getMouse().getAge() != null ? properties.getMouse().getAge() : 5);
    }

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Mouse getMouse() {
        return mouse;
    }

    public void setMouse(Mouse mouse) {
        this.mouse = mouse;
    }

    public void play(){
        System.out.println("cat: " + cat.getName() + ", cat.age = " + cat.getAge() +
                         " mouse: name = " + mouse.getName() + ", age =  "+ mouse.getAge());
    }
}

但是这时候会有一个缺陷,那就是当CartoonCatAndMouse不需要绑定配置文件的属性的时候,那么这时候是并不需要CartoonCatAndMouseProperties,但是实际上不管是否需要,都已经将CartoonCatAndMouseProperties注入到bean中,这时候我们如何解决这个问题,也即当加载CartoonCatAndMouse的时候,自动地将CartoonCatAndMouseProperties注入到spring容器中,当不需要加载的时候,就不会将其注入呢?这时候我们只需要在CartoonCatAndMouseProperties上面的@Component注解去掉,并且在CartoonCatAndMouse类的上面添加注解@EnableConfigurationProperties(CartoonCatAndMouseProperties.class),这样就可以在需要加载CartoonCatAndMouse的时候,自动将CartoonCatAndMouseProperties加载到spring容器中了,不需要的时候就不会添加。

自动配置原理

我们启动spring boot项目的时候,它是要进入到启动类的,那么这时候,这个启动类中含有注解@SpringBootApplication,这个注解的代码如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication

其中关键的注解是:@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan

  • @SpringBootConfiguration的代码如下所示:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration 
    public @interface SpringBootConfiguration
    

    所以注解@SpringBootConfiguration主要是依赖于注解@Configuration,而@Configuration注解作用相当于@Component(通过观察源码可以知道),用来添加bean,但是却又与@Component不同,因为@Configuration注解中含有属性proxyMethodBean,默认值是true,这样的话,如果我们在配置类中利用注解@Bean来将方法的返回值添加到spring容器中的时候,那么这时候在测试类中通过调用这个方法获取到的对象是和从spring容器中取出的是同一个对象,而如果是通过@Component的话,那么这时候每次调用这个方法取到的对象都是新建的。

  • @ComponentScan主要是用于组件扫描,而括号里面的值则是一个过滤器,表示哪些是不需要扫描的

  • @EnabelAutoConfiguration的代码如下所示:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration{
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    

    通过观察代码,可以知道,@EnableAutoConfiguration注解是依赖于@AutoConfigurationPackage以及@Import注解的额,并且还有两个属性值exclude,excludeName,这两个属性值主要是说明哪些类是不需要添加到spring容器中作为bean的,因为注解@SpringBootApplication依赖于这个注解,所以同样拥有这两个属性,所以也可以通过设置这两个属性值来设置哪些类不需要添加到bean中

    那么这时候我们重点来看@EnableAutoConfiguration中的两个注解@AutoConfigrationPackage以及@Import注解。

    • @AutoConfigurationPackage的代码如下所示:

      @Import({Registrar.class})
      public @interface AutoConfigurationPackage 
      

      说明@AutoConfigurationPackage主要依赖于注解@Import({Registrar.class}),然后我们来看Registrar这个类的源代码:

      static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
              Registrar() {
              }
      
              public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                  AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
              }
      
              public Set<Object> determineImports(AnnotationMetadata metadata) {
                  return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
              }
          }
      

      因为这个类实现了接口ImportBeanDefinitionRegistrar,所以这个类是用来注入bean的,通过观察它的方法regiserBeanDefinitions,通过调试可以知道,(String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])的值是我们项目的启动类所在的包名,然后将这个包及其子包下面的类进行扫描,然后添加到spring容器中。所以可以知道注解@AutoConfigurationPackage是用来扫描启动类所在的包以及子包下面的类,将其添加到spring容器中。

    • @Import({AutoConfigurationImportSelector.class})

      我们来到AutoConfigurationImportSelector.class类中,重点看它的代码:

      public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
                  Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
                      return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
                  });
                  AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
                  this.autoConfigurationEntries.add(autoConfigurationEntry);
                  Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
      
                  while(var4.hasNext()) {
                      String importClassName = (String)var4.next();
                      this.entries.putIfAbsent(importClassName, annotationMetadata);
                  }
      
      }
              
              
      protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
              if (!this.isEnabled(annotationMetadata)) {
                  return EMPTY_ENTRY;
              } else {
                  /*
                  这一步获得的之是@EnableAutoConfiguration中的属性值,也即是
                  说attributes是我们不希望添加到spring容器中的值
                  */
                  AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                  /*
                  调用方法getCandidateConfigurations,将可以得到我们将上面attributes去掉之后的
                  类,那么这些类是可能添加到spring容器中的
                  */
                  List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                  configurations = this.removeDuplicates(configurations);
                  Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                  this.checkExcludedClasses(configurations, exclusions);
                  configurations.removeAll(exclusions);
                  configurations = this.getConfigurationClassFilter().filter(configurations);
                  this.fireAutoConfigurationImportEvents(configurations, exclusions);
                  return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
              }
      }  
      
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
          /*
          通过调用方法loadFactoryNames,那么就可以获取到可能添加到spring容器中的类的全路径名
          */
              List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
              Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
              return configurations;
       }
      
        public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
              String factoryTypeName = factoryType.getName();
              return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
      }
      
      
      private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
              MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
              if (result != null) {
                  return result;
              } else {
                  try {
                      /*
                      将META-INF包下面的spring.factories文件的内容,添加到spring容器中,然后返回
                      */
                      Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                      LinkedMultiValueMap result = new LinkedMultiValueMap();
      
                      while(urls.hasMoreElements()) {
                          URL url = (URL)urls.nextElement();
                          UrlResource resource = new UrlResource(url);
                          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                          Iterator var6 = properties.entrySet().iterator();
      
                          while(var6.hasNext()) {
                              Entry<?, ?> entry = (Entry)var6.next();
                              String factoryTypeName = ((String)entry.getKey()).trim();
                              String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                              int var10 = var9.length;
      
                              for(int var11 = 0; var11 < var10; ++var11) {
                                  String factoryImplementationName = var9[var11];
                                  result.add(factoryTypeName, factoryImplementationName.trim());
                              }
                          }
                      }
      
                      cache.put(classLoader, result);
                      return result;
                  } catch (IOException var13) {
                      throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
                  }
              }
          }
      

    我们的测试代码如下所示:

    @SpringBootApplication
    public class SpringbootBeanPropertiesApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext app = SpringApplication.run(SpringbootBeanPropertiesApplication.class, args);
            String[] names = app.getBeanDefinitionNames();
            for(String name : names){
                System.out.println(name);
            }
        }
    
    }
    

    运行结果中就会多出一些我们不知道的类,原因就是上面将META-INF包下面的spring.factories文件中的内容添加到了spring容器中了。所以如果我们需要实现自动配置,那么我们同样可以在resources包下面定义META-INF/spring.factoreis文件:
    在这里插入图片描述
    然后按照对应的格式,设置org.springframework.boot.autoconfigure.EnableAutoConfiguration=\的值是我们希望自动配置的类的全路径名,这样就可以将对应的类自动配置到spring容器中了。同时我们也可以利用注解@ConditionalOnxxxx来控制bean的注入,也可以利用注解@EnableConfigurationProperties来实现它的依赖配置。例如redis就是这样设置的.

相关文章:

  • WRF4.2安装过程全记录
  • 文本文件的编码格式
  • Mediapipe 在Android studio 运行官方的 FaceDetection
  • Java项目源码下载S2SH基于java的保险业务管理系统
  • CS5086E 双节锂电升压充电管理IC特点及应用
  • linux 输出重定向
  • win10任务栏卡死桌面正常的解决方法
  • NVIDIA:应将USD作为3D互联网的HTML标准语言
  • H5画布绘制笑脸
  • 【定制项目】【M14 监测预警平台】百度地图区域绘制(时间轴)/柱状图/仪表图 - 关键技术 python flask + echarts
  • RS笔记:深度推荐模型之SIM(基于搜索的超长行为序列上的用户长期兴趣建模)[CIKM 2020, 阿里妈妈广告团队]
  • 关于第一次接入Kotlin
  • C++中菱形类关系再理解
  • stack和queue的使用和模拟实现
  • 【C++】 string类常用接口的实现
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • [译]如何构建服务器端web组件,为何要构建?
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • django开发-定时任务的使用
  • ES6 学习笔记(一)let,const和解构赋值
  • HTTP请求重发
  • HTTP中的ETag在移动客户端的应用
  • Java方法详解
  • java正则表式的使用
  • 如何进阶一名有竞争力的程序员?
  • 使用putty远程连接linux
  • 项目实战-Api的解决方案
  • 正则学习笔记
  • 7行Python代码的人脸识别
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​你们这样子,耽误我的工作进度怎么办?
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (¥1011)-(一千零一拾一元整)输出
  • (1)虚拟机的安装与使用,linux系统安装
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (python)数据结构---字典
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (分布式缓存)Redis哨兵
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (区间dp) (经典例题) 石子合并
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (原)Matlab的svmtrain和svmclassify
  • (转)菜鸟学数据库(三)——存储过程
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • (转)详解PHP处理密码的几种方式
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout
  • @param注解什么意思_9000字,通俗易懂的讲解下Java注解
  • []T 还是 []*T, 这是一个问题
  • [20150904]exp slow.txt
  • [Firefly-Linux] RK3568 pca9555芯片驱动详解
  • [Gamma]阶段测试报告