飞道的博客

面试阿里p7,被按在地上摩擦,鬼知道我经历了什么?

300人阅读  评论(0)

面试阿里p7被问到的问题(当时我只知道第一个):

  1. @Conditional是做什么的?

  2. @Conditional多个条件是什么逻辑关系?

  3. 条件判断在什么时候执行?

  4. ConfigurationCondition和Condition有什么区别?什么时候使用ConfigurationCondition?

  5. 多个Condition执行的顺序是什么样的?可以配置优先级么?

  6. 可以介绍一下@Conditional常见的一些用法么?

@Conditional注解

@Conditional注解是从spring4.0才有的,可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。

比如可以通过@Conditional来控制bean是否需要注册,控制被@Configuration标注的配置类是需要需要被解析等。

效果就像这段代码,相当于在spring容器解析目标前面加了一个条件判断:


   
  1. if(@Conditional中配置的多个条件是否都匹配){
  2. //spring继续处理被@Conditional注解标注的对象
  3. }

@Conditional源码:


   
  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @ interface Conditional {
  5.     Class<? extends Condition>[] value();
  6. }

这个注解只有一个value参数,Condition类型的数组,Condition是一个接口,表示一个条件判断,内部有个方法返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立。

下面我们来看一下Condition接口。

Condition接口

用来表示条件判断的接口,源码如下:


   
  1. @FunctionalInterface
  2. public  interface Condition {
  3.      /**
  4.      * 判断条件是否匹配
  5.      * context:条件判断上下文
  6.      */
  7.     boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
  8. }

是一个函数式接口,内部只有一个matches方法,用来判断条件是否成立的,2个参数:

  • context:条件上下文,ConditionContext接口类型的,可以用来获取容器中的个人信息

  • metadata:用来获取被@Conditional标注的对象上的所有注解信息

ConditionContext接口

这个接口中提供了一些常用的方法,可以用来获取spring容器中的各种信息,看一下源码:


   
  1. public  interface ConditionContext {
  2.      /**
  3.      * 返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
  4.      */
  5.     BeanDefinitionRegistry getRegistry();
  6.      /**
  7.      * 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
  8.      */
  9.     @Nullable
  10.     ConfigurableListableBeanFactory getBeanFactory();
  11.      /**
  12.      * 返回当前spring容器的环境配置信息对象
  13.      */
  14.     Environment getEnvironment();
  15.      /**
  16.      * 返回资源加载器
  17.      */
  18.     ResourceLoader getResourceLoader();
  19.      /**
  20.      * 返回类加载器
  21.      */
  22.     @Nullable
  23.     ClassLoader getClassLoader();
  24. }

比较关键性的问题:条件判断在什么时候执行?

Spring对配置类的处理主要分为2个阶段:

配置类解析阶段

会得到一批配置类的信息,和一些需要注册的bean

bean注册阶段

将配置类解析阶段得到的配置类和需要注册的bean注册到spring容器中

看一下什么是配置类

类中有下面任意注解之一的就属于配置类:

  1. 类上有@Compontent注解

  2. 类上有@Configuration注解

  3. 类上有@CompontentScan注解

  4. 类上有@Import注解

  5. 类上有@ImportResource注解

  6. 类中有@Bean标注的方法

判断一个类是不是一个配置类,是否的是下面这个方法,有兴趣的可以看一下:

org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate

spring中处理这2个过程会循环进行,直到完成所有配置类的解析及所有bean的注册。

Spring对配置类处理过程

源码位置:

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

整个过程大致的过程如下:

  1. 通常我们会通过new AnnotationConfigApplicationContext()传入多个配置类来启动spring容器

  2. spring对传入的多个配置类进行解析

  3. 配置类解析阶段:这个过程就是处理配置类上面6中注解的过程,此过程中又会发现很多新的配置类,比如@Import导入的一批新的类刚好也符合配置类,而被@CompontentScan扫描到的一些类刚好也是配置类;此时会对这些新产生的配置类进行同样的过程解析

  4. bean注册阶段:配置类解析后,会得到一批配置类和一批需要注册的bean,此时spring容器会将这批配置类作为bean注册到spring容器,同样也会将这批需要注册的bean注册到spring容器

  5. 经过上面第3个阶段之后,spring容器中会注册很多新的bean,这些新的bean中可能又有很多新的配置类

  6. Spring从容器中将所有bean拿出来,遍历一下,会过滤得到一批未处理的新的配置类,继续交给第3步进行处理

  7. step3到step6,这个过程会经历很多次,直到完成所有配置类的解析和bean的注册

从上面过程中可以了解到:

  1. 可以在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解析,那么这个配置上面6种注解的解析都会被跳过

  2. 可以在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到spring容器中

  3. 如果配置类不会被注册到容器,那么这个配置类解析所产生的所有新的配置类及所产生的所有新的bean都不会被注册到容器

一个配置类被spring处理有2个阶段:配置类解析阶段、bean注册阶段(将配置类作为bean被注册到spring容器)。

如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段都有效,此时通过Condition是无法精细的控制某个阶段的,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition

ConfigurationCondition接口

看一下这个接口的源码:


   
  1. public  interface ConfigurationCondition extends Condition {
  2.      /**
  3.      * 条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
  4.      */
  5.     ConfigurationPhase getConfigurationPhase();
  6.      /**
  7.      * 表示阶段的枚举:2个值
  8.      */
  9.     enum ConfigurationPhase {
  10.          /**
  11.          * 配置类解析阶段,如果条件为false,配置类将不会被解析
  12.          */
  13.         PARSE_CONFIGURATION,
  14.          /**
  15.          * bean注册阶段,如果为false,bean将不会被注册
  16.          */
  17.         REGISTER_BEAN
  18.     }
  19. }

ConfigurationCondition接口相对于Condition接口多了一个getConfigurationPhase方法,用来指定条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤。

@Conditional使用的3步骤

  1. 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法

  2. 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型

  3. 启动spring容器加载资源,此时@Conditional就会起作用了

案例1:阻止配置类的处理

在配置类上面使用@Conditional,这个注解的value指定的Condition当有一个为false的时候,spring就会跳过处理这个配置类。

自定义一个Condition类:


   
  1. package com.javacode2018.lesson001.demo25.test3;
  2. import org.springframework.context.annotation.Condition;
  3. import org.springframework.context.annotation.ConditionContext;
  4. import org.springframework.core. type.AnnotatedTypeMetadata;
  5. public class MyCondition1 implements Condition {
  6.     @Override
  7.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  8.          return  false;
  9.     }
  10. }

matches方法内部我们可以随意发挥,此处为了演示效果就直接返回false。

来个配置类,在配置类上面使用上面这个条件,此时会让配置类失效,如下:


   
  1. package com.javacode2018.lesson001.demo25.test3;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Conditional;
  4. import org.springframework.context.annotation.Configuration;
  5. @Conditional(MyCondition1.class)  //@1
  6. @Configuration
  7. public class MainConfig3 {
  8.     @Bean
  9.     public String name() {  //@1
  10.          return  "路人甲Java";
  11.     }
  12. }

@1:使用了自定义的条件类

@2:通过@Bean标注这name这个方法,如果这个配置类成功解析,会将name方法的返回值作为bean注册到spring容器

来个测试类,启动spring容器加载MainConfig3配置类,如下:


   
  1. package com.javacode2018.lesson001.demo25;
  2. import com.javacode2018.lesson001.demo25.test3.MainConfig3;
  3. import org.junit.Test;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import java.util.Map;
  6. public class ConditionTest {
  7.     @Test
  8.     public void test3() {
  9.         AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(MainConfig3.class);
  10.         Map<String, String> serviceMap = context.getBeansOfType(String.class);
  11.         serviceMap.forEach((beanName, bean) -> {
  12.             System.out. println(String.format( "%s->%s", beanName, bean));
  13.         });
  14.     }
  15. }

test3中,从容器中获取String类型的bean,运行test3没有任何输出。

我们可以将MainConfig3上面的@Conditional去掉,再次运行输出:

name->路人甲Java

案例2:阻止bean的注册

来个配置类,如下:


   
  1. package com.javacode2018.lesson001.demo25.test4;
  2. import com.javacode2018.lesson001.demo25.test3.MyCondition1;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Conditional;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration
  7. public class MainConfig4 {
  8.     @Conditional(MyCondition1.class)  //@1
  9.     @Bean
  10.     public String name() {
  11.          return  "路人甲Java";
  12.     }
  13.     @Bean
  14.     public String address() {
  15.          return  "上海市";
  16.     }
  17. }

上面2个方法上面使用了@Bean注解来定义了2个bean,name方法上面使用了@Conditional注解,这个条件会在name这个bean注册到容器之前会进行判断,当条件为true的时候,name这个bean才会被注册到容器。

ConditionTest中新增个测试用例来加载上面这个配置类,从容器中获取String类型所有bean输出,代码如下:


   
  1. @Test
  2. public void test4() {
  3.     AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(MainConfig4.class);
  4.     Map<String, String> serviceMap = context.getBeansOfType(String.class);
  5.     serviceMap.forEach((beanName, bean) -> {
  6.         System.out. println(String.format( "%s->%s", beanName, bean));
  7.     });
  8. }

运行输出:

address->上海市

可以看到容器中只有一个address被注册了,而name这个bean没有被注册。

案例3:bean不存在的时候才注册

需求

IService接口有两个实现类Service1和Service1,这两个类会放在2个配置类中通过@Bean的方式来注册到容器,此时我们想加个限制,只允许有一个IService类型的bean被注册到容器。

可以在@Bean标注的2个方法上面加上条件限制,当容器中不存在IService类型的bean时,才将这个方法定义的bean注册到容器,下面来看代码实现。

代码实现

条件判断类:OnMissingBeanCondition


   
  1. package com.javacode2018.lesson001.demo25.test1;
  2. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  3. import org.springframework.context.annotation.Condition;
  4. import org.springframework.context.annotation.ConditionContext;
  5. import org.springframework.context.annotation.ConfigurationCondition;
  6. import org.springframework.core. type.AnnotatedTypeMetadata;
  7. import java.util.Map;
  8. public class OnMissingBeanCondition implements Condition {
  9.     @Override
  10.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  11.          //获取bean工厂
  12.         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  13.          //从容器中获取IService类型bean
  14.         Map<String, IService> serviceMap = beanFactory.getBeansOfType(IService.class);
  15.          //判断serviceMap是否为空
  16.          return serviceMap.isEmpty();
  17.     }
  18. }

上面matches方法中会看容器中是否存在IService类型的bean,不存在的时候返回true

IService接口


   
  1. package com.javacode2018.lesson001.demo25.test1;
  2. public  interface IService {
  3. }

接口有2个实现类

Service1

   
  1. package com.javacode2018.lesson001.demo25.test1;
  2. public class Service1 implements IService {
  3. }
Service2

   
  1. package com.javacode2018.lesson001.demo25.test1;
  2. public class Service2 implements IService {
  3. }

来一个配置类负责注册Service1到容器


   
  1. package com.javacode2018.lesson001.demo25.test1;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Conditional;
  4. import org.springframework.context.annotation.Configuration;
  5. @Configuration
  6. public class BeanConfig1 {
  7.     @Conditional(OnMissingBeanCondition.class)  //@1
  8.     @Bean
  9.     public IService service1() {
  10.          return  new Service1();
  11.     }
  12. }

@1:方法之前使用了条件判断

再来一个配置类负责注册Service2到容器


   
  1. package com.javacode2018.lesson001.demo25.test1;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Conditional;
  4. import org.springframework.context.annotation.Configuration;
  5. @Configuration
  6. public class BeanConfig2 {
  7.     @Conditional(OnMissingBeanCondition.class) //@1
  8.     @Bean
  9.     public IService service2() {
  10.          return  new Service2();
  11.     }
  12. }

@1:方法之前使用了条件判断

来一个总的配置类,导入另外2个配置类


   
  1. package com.javacode2018.lesson001.demo25.test1;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.context.annotation.Import;
  4. @Configuration
  5. @Import({BeanConfig1.class,BeanConfig2.class})  //@1
  6. public class MainConfig1 {
  7. }

@1:通过@Import将其他2个配置类导入

来个测试用例

ConditionTest新增一个方法,方法中从容器中获取IService类型的bean,然后输出:


   
  1. @Test
  2. public void test1() {
  3.     AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(MainConfig1.class);
  4.     Map<String, IService> serviceMap = context.getBeansOfType(IService.class);
  5.     serviceMap.forEach((beanName, bean) -> {
  6.         System.out. println(String.format( "%s->%s", beanName, bean));
  7.     });
  8. }

运行输出:

service1->com.javacode2018.lesson001.demo25.test1.Service1@2cd76f31

可以看出容器中只有一个IService类型的bean。

可以将@Bean标注的2个方法上面的@Conditional去掉,再运行会输出:


   
  1. service1->com.javacode2018.lesson001.demo25.test1.Service1@ 49438269
  2. service2->com.javacode2018.lesson001.demo25.test1.Service2@ba2f4ec

此时没有条件限制,2个Service都会注册到容器。

案例4:根据环境选择配置类

平常我们做项目的时候,有开发环境、测试环境、线上环境,每个环境中有些信息是不一样的,比如数据库的配置信息,下面我们来模拟不同环境中使用不同的配置类来注册不同的bean。

自定义一个条件的注解


   
  1. package com.javacode2018.lesson001.demo25.test2;
  2. import org.springframework.context.annotation.Conditional;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Conditional(EnvCondition.class)  //@1
  8. @Target(ElementType.TYPE)
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @ interface EnvConditional {
  11.      //环境(测试环境、开发环境、生产环境)
  12.     enum Env {  //@2
  13.         TEST, DEV, PROD
  14.     }
  15.      //环境
  16.     Env value()  default Env.DEV;  //@3
  17. }

@1:注意这个注解比较特别,这个注解上面使用到了@Conditional注解,这个地方使用到了一个自定义Conditione类:EnvCondition

@2:枚举,表示环境,定义了3个环境

@3:这个参数用指定环境

上面这个注解一会我们会用在不同环境的配置类上面

下面来3个配置类

让3个配置类分别在不同环境中生效,会在这些配置类上面使用上面自定义的@EnvConditional注解来做条件限定。

每个配置类中通过@Bean来定义一个名称为name的bean,一会通过输出这个bean来判断哪个配置类生效了。

下面来看3个配置类的代码

测试环境配置类


   
  1. package com.javacode2018.lesson001.demo25.test2;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. @EnvConditional(EnvConditional.Env.TEST) //@1
  6. public class TestBeanConfig {
  7.     @Bean
  8.     public String name() {
  9.          return  "我是测试环境!";
  10.     }
  11. }

@1指定的测试环境

开发环境配置类


   
  1. package com.javacode2018.lesson001.demo25.test2;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. @EnvConditional(EnvConditional.Env.DEV)  //@1
  6. public class DevBeanConfig {
  7.     @Bean
  8.     public String name() {
  9.          return  "我是开发环境!";
  10.     }
  11. }

@1:指定的开发环境

生产环境配置类


   
  1. package com.javacode2018.lesson001.demo25.test2;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. @EnvConditional(EnvConditional.Env.PROD)  //@1
  6. public class ProdBeanConfig {
  7.     @Bean
  8.     public String name() {
  9.          return  "我是生产环境!";
  10.     }
  11. }

@1:指定的生产环境

下面来看一下条件类:EnvCondition

条件类会解析配置类上面@EnvConditional注解,得到环境信息。

然后和目前的环境对比,决定返回true还是false,如下:


   
  1. package com.javacode2018.lesson001.demo25.test2;
  2. import org.springframework.context.annotation.Condition;
  3. import org.springframework.context.annotation.ConditionContext;
  4. import org.springframework.core. type.AnnotatedTypeMetadata;
  5. public class EnvCondition implements Condition {
  6.     @Override
  7.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  8.          //当前需要使用的环境
  9.         EnvConditional.Env curEnv = EnvConditional.Env.DEV;  //@1
  10.          //获取使用条件的类上的EnvCondition注解中对应的环境
  11.         EnvConditional.Env env = (EnvConditional.Env) metadata.getAllAnnotationAttributes(EnvConditional.class.getName()).get( "value").get( 0);
  12.          return env.equals(curEnv);
  13.     }
  14. }

@1:这个用来指定当前使用的环境,此处假定当前使用的是开发环境,这个我们以后可以任意发挥,比如将这些放到配置文件中,此处方便演示效果。

来个测试用例


   
  1. @Test
  2. public void test2() {
  3.     AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(MainConfig2.class);
  4.     System.out. println(context.getBean( "name"));
  5. }

运行输出

我是开发环境!

可以看到开发环境生效了。

修改一下EnvCondition的代码,切换到生产环境:

EnvConditional.Env curEnv = EnvConditional.Env.PROD;

再次运行test2方法输出:

我是生产环境!

生产环境配置类生效了。

案例5:Condition指定优先级

多个Condition按顺序执行

@Condtional中value指定多个Condtion的时候,默认情况下会按顺序执行,还是通过代码来看一下效果。

下面代码中定义了3个Condition,每个Condition的matches方法中会输出当前类名,然后在配置类上面同时使用这3个Condition:


   
  1. package com.javacode2018.lesson001.demo25.test5;
  2. import org.springframework.context.annotation.Condition;
  3. import org.springframework.context.annotation.ConditionContext;
  4. import org.springframework.context.annotation.Conditional;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.core. type.AnnotatedTypeMetadata;
  7. class Condition1 implements Condition {
  8.     @Override
  9.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  10.         System.out. println(this.getClass().getName());
  11.          return  true;
  12.     }
  13. }
  14. class Condition2 implements Condition {
  15.     @Override
  16.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  17.         System.out. println(this.getClass().getName());
  18.          return  true;
  19.     }
  20. }
  21. class Condition3 implements Condition {
  22.     @Override
  23.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  24.         System.out. println(this.getClass().getName());
  25.          return  true;
  26.     }
  27. }
  28. @Configuration
  29. @Conditional({Condition1.class, Condition2.class, Condition3.class})
  30. public class MainConfig5 {
  31. }

来个测试用例


   
  1. @Test
  2. public void test5() {
  3.     AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(MainConfig5.class);
  4. }

运行输出:


   
  1. com.javacode2018.lesson001.demo25.test5.Condition1
  2. com.javacode2018.lesson001.demo25.test5.Condition2
  3. com.javacode2018.lesson001.demo25.test5.Condition3
  4. com.javacode2018.lesson001.demo25.test5.Condition1
  5. com.javacode2018.lesson001.demo25.test5.Condition2
  6. com.javacode2018.lesson001.demo25.test5.Condition3
  7. com.javacode2018.lesson001.demo25.test5.Condition1
  8. com.javacode2018.lesson001.demo25.test5.Condition2
  9. com.javacode2018.lesson001.demo25.test5.Condition3

上面有多行输出,是因为spring解析整个配置类的过程中,有好几个地方都会执行条件判断。

咱们只用关注前3行,可以看出输出的属性和@Conditional中value值的顺序是一样的。

指定Condition的顺序

自定义的Condition可以实现PriorityOrdered接口或者继承Ordered接口,或者使用@Order注解,通过这些来指定这些Condition的优先级。

排序规则:先按PriorityOrdered排序,然后按照order的值进行排序;也就是:PriorityOrdered asc,order值 asc


   
  1. 下面这几个都可以指定order的值
  2. 接口:org.springframework.core.Ordered,有个getOrder方法用来返回 int类型的值
  3. 接口:org.springframework.core.PriorityOrdered,继承了Ordered接口,所以也有getOrder方法
  4. 注解:org.springframework.core.annotation.Order,有个 int类型的value参数指定Order的大小

看案例代码:


   
  1. package com.javacode2018.lesson001.demo25.test6;
  2. import org.springframework.context.annotation.Condition;
  3. import org.springframework.context.annotation.ConditionContext;
  4. import org.springframework.context.annotation.Conditional;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.core.Ordered;
  7. import org.springframework.core.PriorityOrdered;
  8. import org.springframework.core.annotation.Order;
  9. import org.springframework.core. type.AnnotatedTypeMetadata;
  10. @Order( 1//@1
  11. class Condition1 implements Condition {
  12.     @Override
  13.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  14.         System.out. println(this.getClass().getName());
  15.          return  true;
  16.     }
  17. }
  18. class Condition2 implements Condition, Ordered {  //@2
  19.     @Override
  20.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  21.         System.out. println(this.getClass().getName());
  22.          return  true;
  23.     }
  24.     @Override
  25.     public  int getOrder() {  //@3
  26.          return  0;
  27.     }
  28. }
  29. class Condition3 implements Condition, PriorityOrdered {  //@4
  30.     @Override
  31.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  32.         System.out. println(this.getClass().getName());
  33.          return  true;
  34.     }
  35.     @Override
  36.     public  int getOrder() {
  37.          return  1000;
  38.     }
  39. }
  40. @Configuration
  41. @Conditional({Condition1.class, Condition2.class, Condition3.class}) //@5
  42. public class MainConfig6 {
  43. }

@1:Condition1通过@Order指定顺序,值为1

@2:Condition2通过实现了Ordered接口来指定顺序,@3:getOrder方法返回1

@4:Condition3实现了PriorityOrdered接口,实现这个接口需要重写getOrder方法,返回1000

@5:Condtion顺序为1、2、3

根据排序的规则,PriorityOrdered的会排在前面,然后会再按照order升序,最后可以顺序是:

Condtion3->Condtion2->Condtion1

来个测试用例看看效果是不是我们分析的这样:


   
  1. @Test
  2. public void test6() {
  3.     AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(MainConfig6.class);
  4. }

运行test6,部分输出如下:


   
  1. com.javacode2018.lesson001.demo25.test6.Condition3
  2. com.javacode2018.lesson001.demo25.test6.Condition2
  3. com.javacode2018.lesson001.demo25.test6.Condition1

结果和我们分析的一致。

案例6:ConfigurationCondition使用

ConfigurationCondition使用的比较少,很多地方对这个基本上也不会去介绍,Condition接口基本上可以满足99%的需求了,但是springboot中却大量用到了ConfigurationCondition这个接口。

ConfigurationCondition通过解释比较难理解,来个案例感受一下:

来一个普通的类:Service


   
  1. package com.javacode2018.lesson001.demo25.test7;
  2. public class Service {
  3. }

来一个配置类,通过配置类注册上面这个Service


   
  1. package com.javacode2018.lesson001.demo25.test7;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. public class BeanConfig1 {
  6.     @Bean
  7.     public Service service() {
  8.          return  new Service();
  9.     }
  10. }

再来一个配置类:BeanConfig2


   
  1. package com.javacode2018.lesson001.demo25.test7;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. public class BeanConfig2 {
  6.     @Bean
  7.     public String name() {
  8.          return  "路人甲Java";
  9.     }
  10. }

来一个总的配置类


   
  1. package com.javacode2018.lesson001.demo25.test7;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.context.annotation.Import;
  4. @Configuration
  5. @Import({BeanConfig1.class, BeanConfig2.class})
  6. public class MainConfig7 {
  7. }

上面通过@Import引入了另外2个配置类

来个测试用例加载MainConfig7配置类


   
  1. @Test
  2. public void test7() {
  3.     AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(MainConfig7.class);
  4.     context.getBeansOfType(String.class).forEach((beanName, bean) -> {
  5.         System.out. println(String.format( "%s->%s", beanName, bean));
  6.     });
  7. }

上面从容器中获取String类型的bean,然后输出。

运行输出

name->路人甲Java

现在我们有个需求

当容器中有Service这种类型的bean的时候,BeanConfig2才生效。

很简单吧,加个Condition就行了,内部判断容器中是否有Service类型的bean,继续

来个自定义的Condition


   
  1. package com.javacode2018.lesson001.demo25.test7;
  2. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  3. import org.springframework.context.annotation.Condition;
  4. import org.springframework.context.annotation.ConditionContext;
  5. import org.springframework.core. type.AnnotatedTypeMetadata;
  6. public class MyCondition1 implements Condition {
  7.     @Override
  8.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  9.          //获取spring容器
  10.         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  11.          //判断容器中是否存在Service类型的bean
  12.         boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
  13.          return existsService;
  14.     }
  15. }

上面代码很简单,判断容器中是否有IService类型的bean。

BeanConfig2上使用Condition条件判断


   
  1. @Configuration
  2. @Conditional(MyCondition1.class)
  3. public class BeanConfig2 {
  4.     @Bean
  5.     public String name() {
  6.          return  "路人甲Java";
  7.     }
  8. }

再次运行test7输出

无任何输出

为什么?

在文章前面我们说过,配置类的处理会依次经过2个阶段:配置类解析阶段和bean注册阶段,Condition接口类型的条件会对这两个阶段都有效,解析阶段的时候,容器中是还没有Service这个bean的,配置类中通过@Bean注解定义的bean在bean注册阶段才会被注册到spring容器,所以BeanConfig2在解析阶段去容器中是看不到Service这个bean的,所以就被拒绝了。

此时我们需要用到ConfigurationCondition了,让条件判断在bean注册阶段才起效。

自定义一个ConfigurationCondition类


   
  1. package com.javacode2018.lesson001.demo25.test7;
  2. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  3. import org.springframework.context.annotation.ConditionContext;
  4. import org.springframework.context.annotation.ConfigurationCondition;
  5. import org.springframework.core. type.AnnotatedTypeMetadata;
  6. public class MyConfigurationCondition1 implements ConfigurationCondition {
  7.     @Override
  8.     public ConfigurationPhase getConfigurationPhase() {
  9.          return ConfigurationPhase.REGISTER_BEAN;  //@1
  10.     }
  11.     @Override
  12.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  13.          //获取spring容器
  14.         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  15.          //判断容器中是否存在Service类型的bean
  16.         boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
  17.          return existsService;
  18.     }
  19. }

@1:指定条件在bean注册阶段,这个条件才有效

matches方法中的内容直接复制过来,判断规则不变。

修改BeanConfig2的类容


   
  1. @Conditional(MyCondition1.class)
  2. 替换为
  3. @Conditional(MyConfigurationCondition1.class)

再次运行test7输出

name->路人甲Java

此时name这个bean被输出了。

可以再试试将BeanConfig1中service方法上面的@Bean去掉,此时Service就不会被注册到容器,再运行一下test7,会发现没有输出了,此时BeanConfig2会失效。

判断bean存不存在的问题,通常会使用ConfigurationCondition这个接口,阶段为:REGISTER_BEAN,这样可以确保条件判断是在bean注册阶段执行的。

对springboot比较熟悉的,它里面有很多@Conditionxxx这样的注解,可以去看一下这些注解,很多都实现了ConfigurationCondition接口。

Spring中这块的源码

@Conditional注解是被下面这个类处理的

org.springframework.context.annotation.ConfigurationClassPostProcessor

又是这个类,说了很多次了,非常重要的一个类,大家下去了多撸一下这个类的源码,这样理解起来更顺畅一些。

案例源码

https://gitee.com/javacode2018/spring-series

路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。

总结

  1. @Conditional注解可以标注在spring需要处理的对象上(配置类、@Bean方法),相当于加了个条件判断,通过判断的结果,让spring觉得是否要继续处理被这个注解标注的对象

  2. spring处理配置类大致有2个过程:解析配置类、注册bean,这两个过程中都可以使用@Conditional来进行控制spring是否需要处理这个过程

  3. Condition默认会对2个过程都有效

  4. ConfigurationCondition控制得更细一些,可以控制到具体那个阶段使用条件判断

Spring系列

  1. Spring系列第1篇:为何要学spring?

  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定义详解(-)

  5. Spring系列第5篇:创建bean实例这些方式你们都知道?

  6. Spring系列第6篇:玩转bean scope,避免跳坑里!

  7. Spring系列第7篇:依赖注入之手动注入

  8. Spring系列第8篇:自动注入(autowire)详解,高手在于坚持

  9. Spring系列第9篇:depend-on到底是干什么的?

  10. Spring系列第10篇:primary可以解决什么问题?

  11. Spring系列第11篇:bean中的autowire-candidate又是干什么的?

  12. Spring系列第12篇:lazy-init:bean延迟初始化

  13. Spring系列第13篇:使用继承简化bean配置(abstract & parent)

  14. Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?

  15. Spring系列第15篇:代理详解(Java动态代理&cglib代理)?

  16. Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)

  17. Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)

  18. Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)

  19. Spring系列第18篇:@import详解(bean批量注册)

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. Mybatis系列(共12篇)

  5. 聊聊db和缓存一致性常见的实现方式

  6. 接口幂等性这么重要,它是什么?怎么实现?

  7. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!

路人甲java

▲长按图片识别二维码关注

路人甲Java:工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!


转载:https://blog.csdn.net/likun557/article/details/105108901
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场