注解说明

@Conditional 是 Spring 提供的用于判断是否对 Bean 注入的条件注解(在4.0后版本引入),只有满足了所有的条件,一个 bean 才会被注册成功并使用。此注解可标注在类上、元注解上、@Bean方法上,通常与 @Configuration 注解配合使用,标识在 @Configuration 注解上的 @Conditional 会影响此配置类的所有 @Bean 注解方法、@Import 注解和@ComponentScan 注解。另需注意 @Conditional 注解不支持继承

对于标注在元注解上的样例,可查看 SpringBoot 项目下的 org.springframework.boot.autoconfigure.condition 包以 ConditionalOn 开头的类

注解属性

此注解只有一个属性 value ,返回类型为 Class 数组,它用来配置所有的 Condition 类信息

Condition 是 Spring 提供的一个接口,它提供了 matches 方法来提供判断的依据逻辑,对于其参数 ConditionContext 提供了丰富的上下文信息

测试代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@Configuration
@ComponentScan(basePackages = "cn.imcompany.bean.condition")
public class MyConfiguration {

    @Bean
    @ConditionalOnDate
    public MyBean myBean() {
        return new MyBean();
    }

    @Bean
    public MyBean2 myBean2() {
        return new MyBean2();
    }

    @Bean
    @Conditional(ConfigPropertyCondition.class)
    public MyBean5 myBean5() {
        return new MyBean5();
    }
}

@Configuration
@Conditional(ConfigPropertyCondition.class)
public class MyConfigConfiguration {

    @Bean
    public MyBean3 myBean3() {
        return new MyBean3();
    }

    @Bean
    public MyBean4 myBean4() {
        return new MyBean4();
    }
}

public class MyDateCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        LocalDateTime now = LocalDateTime.now();
        int minute = now.getMinute();
        return minute % 2 == 0; // 每隔两分钟满足条件
    }
}

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})
@Documented
@Conditional(MyDateCondition.class)
public @interface ConditionalOnDate {
}

public class ConfigPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "true".equals(System.getProperty("my.config.property"));
    }
}

public class Main {

    public static void main(String[] args) {

        System.setProperty("my.config.property", "true");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

源码阅读

对于 Configuration 类条件处理分为两个阶段:

  1. PARSE_CONFIGURATION, 解析 @Configuration 类阶段
  2. REGISTER_BEAN,注册Bean阶段,如 @Configuration 类下定义的 @Bean 方法

快速解析Configuration注解类

快速解析 @Conditional 注解的逻辑在 AnnotationConfigApplicationContext的register阶段,此方法会调用 AnnotatedBeanDefinitionReader#doRegisterBean 来对Spring中常用的组件类(@Compnent,@Configuration)进行注入操作,在此方法会调用核心方法 ConditionEvaluator#shouldSkip 来判断要处理类的是否要进行注册。 shouldSkip 会先判断是否标注有 @Conditional 注解,如果没有则进行正常的注册,如果有则会调用 collectConditions() 方法收集注解上的 Condition 信息并进行排序返回(此操作会先从配置类上获取 Condition 类信息再实例化类),然后会遍历 conditions 来进行判断,如果调用某个 Condition的match() 方法返回 false ,则确认此配置类的解析需要跳过

由于测试代码的 MyConfiguration 注解类没有标注 @Conditional 相关的注解,则此类中的其它配置在后续 refresh 阶段会继续进行解析

解析其它配置及配置中的Bean

除了在 AnnotationConfigApplicationContext 的构造方法中提供的配置类解析后,其它的配置类及其类中的配置条件判断都发生在 refresh 阶段,这里面的判断逻辑发生在 @Configuration 配置的解析流程中,主要涉及 @Configuration、@Componenet、@Import、@Bean 这4个注解的解析

解析@Configuration

对于 @Configuration 最终会在 ConfigurationClassParser#processConfigurationClass 中先对 @Configuration 进行条件判断,此方法会调用 ConditionEvaluator#shouldSkip 进行判断,不满足则不再进行后续的解析

解析@ComponentScan

对于 @ComponentScan 扫描的配置类的条件判断发生在 ClassPathScanningCandidateComponentProvider#isCandidateComponent 中,此方法会调用 isConditionMatch() 方法进而来调用 ConditionEvaluator#shouldSkip 来进行判断

解析@Import

对于在 @Configuration 中使用 @Import 注解导入的配置类,要满足导入此配置类的类没有被判断为skip才会对导入的类进行注册,此方法会调用 TrackedConditionEvaluator#shouldSkip 进而调用 ConditionEvaluator#shouldSkip 来进行判断

解析@Bean

对于 @Configuration 中配置的 @Bean 方法返回的类的条件判断发生在 ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass 中,此方法会调用 loadBeanDefinitionsForBeanMethod 方法进而调用 ConditionEvaluator#shouldSkip 来进行判断

聊聊@Profile

Spring 中提供了 @Profile 注解来实现候选组件对特定的一组环境名称满足条件时被注册使用,它就是基于 @Conditional 注解来实现的,此注解关联定义了 ProfileCondition ,在ProfileCondition 对Spring中的环境名进行匹配逻辑的处理

The @Profile annotation is actually implemented by using a much more flexible annotation called @Conditional.

@Profile与@Conditional的区别,@Profile侧重于环境,而@Conditional更通用

总结

Spring 通过提供 @Conditional 注解使 Spring 支持更加灵活的配置类注入控制,特别是支持多个条件配置,其核心处理逻辑在 ConditionEvaluator的shouldSkip方法。开发人员可以通过配置此类来打开关闭相关的功能(典型如Spring Boot的条件化装配),通常开发的基本功能配置可以通过增加 @Conditional 注解来判断只有在特定情况下才使用默认的配置,这对于模块化开发很有帮助。