Skip to content

条件注解机制 ​

深入理解SpringBoot条件注解的工作原理和实现机制,掌握Condition接口、条件评估器的工作原理,学会使用和自定义条件注解。

📋 条件注解概览 ​

SpringBoot的条件注解允许我们根据特定条件来决定是否加载Bean或配置类。

🎯 一、Condition接口 ​

1.1 Condition接口定义 ​

Condition是Spring条件注解的基础接口:

java
@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

方法说明:

  • matches:判断条件是否满足
    • ConditionContext:条件上下文,提供环境、BeanFactory等信息
    • AnnotatedTypeMetadata:注解元数据,提供注解信息

1.2 ConditionContext ​

java
public interface ConditionContext {
    BeanDefinitionRegistry getRegistry();
    ConfigurableListableBeanFactory getBeanFactory();
    Environment getEnvironment();
    ResourceLoader getResourceLoader();
    ClassLoader getClassLoader();
}

ConditionContext提供的能力:

  • BeanDefinitionRegistry:检查Bean定义
  • ConfigurableListableBeanFactory:检查Bean实例
  • Environment:获取配置属性
  • ResourceLoader:加载资源
  • ClassLoader:检查类是否存在

1.3 AnnotatedTypeMetadata ​

java
public interface AnnotatedTypeMetadata {
    boolean isAnnotated(String annotationName);
    Map<String, Object> getAnnotationAttributes(String annotationName);
    Map<String, Object> getAnnotationAttributes(String annotationName, 
            boolean classValuesAsString);
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
    // ...
}

🎯 二、条件注解体系 ​

2.1 类条件注解 ​

@ConditionalOnClass ​

作用:当类路径存在指定类时生效。

java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    Class<?>[] value() default {};
    String[] name() default {};
}

使用示例:

java
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
    // 只有当类路径存在DataSource类时才加载
}

实现原理:

java
// OnClassCondition.getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
        AnnotatedTypeMetadata metadata) {
    ClassLoader classLoader = context.getClassLoader();
    ConditionMessage matchMessage = ConditionMessage.empty();
    
    List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
    if (onClasses != null) {
        List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
        if (!missing.isEmpty()) {
            return ConditionOutcome.noMatch(
                    ConditionMessage.forCondition(ConditionalOnClass.class)
                            .didNotFind("required class", "required classes")
                            .items(Style.QUOTE, missing));
        }
    }
    // ...
    return ConditionOutcome.match(matchMessage);
}

@ConditionalOnMissingClass ​

作用:当类路径不存在指定类时生效。

java
@Configuration
@ConditionalOnMissingClass("com.example.OldService")
public class NewServiceAutoConfiguration {
    // 只有当类路径不存在OldService时才加载
}

2.2 Bean条件注解 ​

@ConditionalOnBean ​

作用:当容器中存在指定Bean时生效。

java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
    Class<?>[] value() default {};
    String[] name() default {};
    Class<?>[] type() default {};
}

使用示例:

java
@Configuration
@ConditionalOnBean(DataSource.class)
public class JdbcTemplateAutoConfiguration {
    // 只有当容器中存在DataSource Bean时才加载
}

实现原理:

java
// OnBeanCondition.getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
        AnnotatedTypeMetadata metadata) {
    ConditionMessage matchMessage = ConditionMessage.empty();
    
    // 检查Bean是否存在
    if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
        BeanSearchSpec spec = new BeanSearchSpec(context, metadata, 
                ConditionalOnBean.class);
        MatchResult matchResult = getMatchingBeans(context, spec);
        if (!matchResult.isAllMatched()) {
            String reason = createOnBeanNoMatchReason(matchResult);
            return ConditionOutcome.noMatch(ConditionMessage.forCondition(
                    ConditionalOnBean.class, spec).because(reason));
        }
        matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
                .found("bean", "beans").items(Style.QUOTE, matchResult.getNames());
    }
    // ...
    return ConditionOutcome.match(matchMessage);
}

@ConditionalOnMissingBean ​

作用:当容器中不存在指定Bean时生效。

java
@Bean
@ConditionalOnMissingBean
public MyService myService() {
    // 只有当容器中不存在MyService Bean时才创建
    return new MyService();
}

2.3 属性条件注解 ​

@ConditionalOnProperty ​

作用:当配置属性满足条件时生效。

java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    String[] value() default {};
    String prefix() default "";
    String[] name() default {};
    String havingValue() default "";
    boolean matchIfMissing() default false;
}

使用示例:

java
@Configuration
@ConditionalOnProperty(prefix = "my.feature", name = "enabled", 
        havingValue = "true", matchIfMissing = false)
public class MyFeatureAutoConfiguration {
    // 只有当my.feature.enabled=true时才加载
}

配置示例:

yaml
# application.yml
my:
  feature:
    enabled: true

实现原理:

java
// OnPropertyCondition.getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
        AnnotatedTypeMetadata metadata) {
    List<AnnotationAttributes> allAnnotationAttributes = 
            metadata.getAnnotations(ConditionalOnProperty.class);
    List<ConditionMessage> noMatch = new ArrayList<>();
    List<ConditionMessage> match = new ArrayList<>();
    for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
        ConditionOutcome outcome = determineOutcome(annotationAttributes, 
                context.getEnvironment());
        (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
    }
    if (!noMatch.isEmpty()) {
        return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
    }
    return ConditionOutcome.match(ConditionMessage.of(match));
}

@ConditionalOnExpression ​

作用:当SpEL表达式结果为true时生效。

java
@Configuration
@ConditionalOnExpression("${my.feature.enabled:false} && ${my.feature.mode} == 'advanced'")
public class AdvancedFeatureConfiguration {
    // 只有当表达式为true时才加载
}

2.4 Web条件注解 ​

@ConditionalOnWebApplication ​

作用:当应用是Web应用时生效。

java
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class WebMvcAutoConfiguration {
    // 只有当应用是Servlet Web应用时才加载
}

@ConditionalOnNotWebApplication ​

作用:当应用不是Web应用时生效。

java
@Configuration
@ConditionalOnNotWebApplication
public class BatchAutoConfiguration {
    // 只有当应用不是Web应用时才加载
}

🎯 三、条件评估流程 ​

3.1 条件评估时机 ​

条件注解在以下时机被评估:

  1. 配置类解析阶段:ConfigurationClassPostProcessor.processConfigBeanDefinitions
  2. Bean定义加载阶段:invokeBeanFactoryPostProcessors
  3. Bean实例化阶段:finishBeanFactoryInitialization

3.2 条件评估流程 ​

3.3 ConditionEvaluator ​

Spring使用ConditionEvaluator来评估条件:

java
public class ConditionEvaluator {
    
    private final ConditionContext context;
    
    public ConditionEvaluator(ResourceLoader resourceLoader, 
            Environment environment) {
        // ...
    }
    
    public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
        return shouldSkip(metadata, null);
    }
    
    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, 
            @Nullable ConfigurationPhase phase) {
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
            return false;
        }
        
        if (phase == null) {
            if (metadata instanceof AnnotationMetadata &&
                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
            }
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        }
        
        List<Condition> conditions = new ArrayList<>();
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, 
                        this.context.getClassLoader());
                conditions.add(condition);
            }
        }
        
        AnnotationAwareOrderComparator.sort(conditions);
        
        for (Condition condition : conditions) {
            ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition) {
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            }
            if (requiredPhase == null || requiredPhase == phase) {
                if (!condition.matches(this.context, metadata)) {
                    return true;
                }
            }
        }
        
        return false;
    }
}

🎯 四、自定义条件注解 ​

4.1 创建自定义Condition ​

示例:环境条件判断

java
public class OnEnvironmentCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext context, 
            AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(
                ConditionalOnEnvironment.class.getName());
        
        String[] environments = (String[]) attributes.get("value");
        String currentEnvironment = context.getEnvironment()
                .getProperty("spring.profiles.active", "default");
        
        return Arrays.asList(environments).contains(currentEnvironment);
    }
}

4.2 创建条件注解 ​

java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnEnvironmentCondition.class)
public @interface ConditionalOnEnvironment {
    String[] value();
}

4.3 使用自定义条件注解 ​

java
@Configuration
@ConditionalOnEnvironment("production")
public class ProductionOnlyConfiguration {
    // 只在生产环境加载
}

🎯 五、条件注解最佳实践 ​

5.1 条件组合 ​

多个条件组合:

java
@Configuration
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class JdbcTemplateAutoConfiguration {
    // 多个条件必须同时满足
}

5.2 条件优先级 ​

条件注解按以下顺序评估:

  1. 类条件:@ConditionalOnClass、@ConditionalOnMissingClass
  2. Bean条件:@ConditionalOnBean、@ConditionalOnMissingBean
  3. 属性条件:@ConditionalOnProperty、@ConditionalOnExpression
  4. Web条件:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication

5.3 条件注解顺序 ​

使用@Order注解控制条件评估顺序:

java
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {
    // 高优先级
}

📖 学习资源 ​

视频教程 ​

网站资源 ​

✅ 学习检查清单 ​

  • [ ] 已理解Condition接口的作用
  • [ ] 已理解ConditionContext的作用
  • [ ] 已掌握类条件注解的使用
  • [ ] 已掌握Bean条件注解的使用
  • [ ] 已掌握属性条件注解的使用
  • [ ] 已理解条件评估流程
  • [ ] 已能够创建自定义条件注解

上一章节:Starter机制
下一章节:配置属性绑定
返回目录:SpringBoot源码深度解析