夯实spring(二十):@Conditional注解详解

布满荆棘的人生 2024-03-03 08:49 181阅读 0赞

1,@Conditional注解

  1. @Target({
  2. ElementType.TYPE, ElementType.METHOD})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. public @interface Conditional {
  6. Class<? extends Condition>[] value();
  7. }
  8. @FunctionalInterface
  9. public interface Condition {
  10. //context:条件上下文,ConditionContext接口类型的,可以用来获取容器中的各种信息
  11. //metadata:用来获取被@Conditional标注的对象上的所有注解信息
  12. boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
  13. }

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

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

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

上面的接口Condition 的方法matches的参数ConditionContext

  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的配置类

  • 类上有@Compontent注解
  • 类上有@Configuration注解
  • 类上有@CompontentScan注解
  • 类上有@Import注解
  • 类上有@ImportResource注解
  • 类中有@Bean标注的方法

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

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

  1. 配置类解析阶段

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

  2. bean注册阶段

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

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

  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. 第三步到第六步,这个过程会经历很多次,直到完成所有配置类的解析和bean的注册

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

对于Condition接口我们可以:

  • 在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解析,那么这个配置上面6种注解的解析都会被跳过。
  • 在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到spring容器中

2,@Conditional的使用

  • 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
  • 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型

来个案例:

自定义一个Condition类实现Condition接口:

  1. public class Condition1 implements Condition {
  2. @Override
  3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  4. return false;
  5. }
  6. }

配置类Config1,在配置类上面使用上面Condition1 这个条件

  1. @Configuration
  2. @Conditional(Condition1.class)
  3. public class Config1{
  4. @Bean
  5. public String hello() {
  6. return "Hello,World";
  7. }
  8. }

测试输出:

  1. public class Main {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config1.class);
  4. String hello = (String) context.getBean("hello");
  5. System.out.println(hello);
  6. }
  7. }

此时会输出报错:

  1. Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'hello' available

我们把Config1的@Conditional(Condition1.class)去掉再运行输出:

  1. Hello,World

上面的案列是阻止配置类的,再来一个阻止bean注册的:

  1. public class Car {
  2. private String name;
  3. private double price;
  4. public Car(String name, double price) {
  5. this.name = name;
  6. this.price = price;
  7. }
  8. }
  9. @Configuration
  10. public class Config2 {
  11. @Bean
  12. @Conditional(Condition1.class)
  13. public Car car1() {
  14. return new Car("五菱",10000);
  15. }
  16. @Bean
  17. public Car car2() {
  18. return new Car("五菱MINI",20000);
  19. }
  20. @Override
  21. public String toString() {
  22. return "Car{" +
  23. "name='" + name + '\'' +
  24. ", price=" + price +
  25. '}';
  26. }
  27. }

上面注册两个bean,car1和car2,car1被@Conditional(Condition1.class)标注

测试输出:

  1. public class Main {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config2.class);
  4. for (String name : context.getBeanDefinitionNames()) {
  5. System.out.println(context.getBean(name));
  6. }
  7. }
  8. }
  9. org.springframework.context.annotation.ConfigurationClassPostProcessor@4c6e276e
  10. org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@534df152
  11. org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@52e677af
  12. org.springframework.context.event.EventListenerMethodProcessor@35083305
  13. org.springframework.context.event.DefaultEventListenerFactory@8e0379d
  14. com.chen.Config2$$EnhancerBySpringCGLIB$$ad8038a0@341b80b2
  15. Car{
  16. name='五菱MINI', price=20000.0}

可以看到只有car2注册了。如果把car1的@Conditional(Condition1.class)去掉,car1就会注册进来。

3,Condition指定优先级

@Condtional中value指定多个Condtion的时候,默认情况下会按顺序执行。我们也可以指定Condition的顺序。自定义的Condition可以实现PriorityOrdered接口或者继承Ordered接口,或者使用@Order注解,通过这些来指定这些Condition的优先级。

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

我们通过使用@Order注解来指定一下多个Condtion的顺序,其他的方法可以自己测试。

来两个Condition的实现类:

  1. @Order(1)
  2. public class Condition1 implements Condition {
  3. @Override
  4. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  5. System.out.println("我是Condition1");
  6. return true;
  7. }
  8. }
  9. @Order(2)
  10. public class Condition2 implements Condition {
  11. @Override
  12. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  13. System.out.println("我是Condition2");
  14. return true;
  15. }
  16. }

来个配置类,使用上面的实现类:

  1. @Configuration
  2. @Conditional(value = {
  3. Condition2.class,Condition1.class})
  4. public class Config3 {
  5. }

注意@Conditional(value =
{Condition2.class,Condition1.class})是Condition2.class,Condition1.class顺序写的

测试输出:

  1. public class Main {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config3.class);
  4. }
  5. }
  6. 我是Condition1
  7. 我是Condition2
  8. 我是Condition1
  9. 我是Condition2
  10. 我是Condition1
  11. 我是Condition2

可以看到是实现输出我是 Condition1,然后才是 我是Condition2。是按照@Order的,不是按照编写顺序的。

4,接口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()

前面说过,配置类的处理会依次经过2个阶段:配置类解析阶段和bean注册阶段,Condition接口类型的条件会对这两个阶段都有效

而ConfigurationCondition接口可以控制在哪个阶段起作用。

如:

  1. public class Condition3 implements ConfigurationCondition {
  2. @Override
  3. public ConfigurationPhase getConfigurationPhase() {
  4. return ConfigurationPhase.REGISTER_BEAN; //指定条件在bean注册阶段,这个条件才有效
  5. }
  6. @Override
  7. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  8. return false;
  9. }
  10. }

总结:

  • @Conditional注解可以标注在spring需要处理的对象上(配置类、@Bean方法),相当于加了个条件判断,通过判断的结果,是否要继续处理被这个注解标注的对象,多个条件是逻辑与的关系
  • spring处理配置类大致有2个过程:解析配置类、注册bean,这两个过程中都可以使用@Conditional来进行控制spring是否需要处理这个过程,Condition默认会对2个过程都有效
  • ConfigurationCondition控制得更细一些,可以控制到具体那个阶段使用条件判断

发表评论

表情:
评论列表 (有 0 条评论,181人围观)

还没有评论,来说两句吧...

相关阅读