一、核心概念

Spring Boot 的条件注解(Conditional Annotations)是其“自动配置”(Auto-configuration)机制的核心。它们允许开发者根据特定条件(如类路径中是否存在某个类、某个 Bean 是否已存在等)来决定是否创建某个 Bean 或加载某个配置类。

核心思想: “约定优于配置” + “按需加载”。Spring Boot 自动配置不是一股脑儿地加载所有组件,而是根据应用的依赖和环境智能地决定哪些配置需要生效。

主要条件注解

  1. @ConditionalOnClass: 当类路径中存在指定的一个或多个类时,条件成立。
  2. @ConditionalOnMissingClass: 当类路径中不存在指定的一个或多个类时,条件成立。
  3. @ConditionalOnBean: 当容器中存在指定的一个或多个 Bean 时,条件成立。
  4. @ConditionalOnMissingBean: 当容器中不存在指定的 Bean 时,条件成立。
  5. @ConditionalOnProperty: 当指定的配置属性具有特定值时,条件成立。
  6. @ConditionalOnResource: 当类路径中存在指定的资源文件时,条件成立。
  7. @ConditionalOnWebApplication: 当应用是 Web 应用时,条件成立。
  8. @ConditionalOnNotWebApplication: 当应用不是 Web 应用时,条件成立。
  9. @ConditionalOnExpression: 当 SpEL 表达式结果为 true 时,条件成立。
  10. @Conditional: 通用注解,可以指定一个实现了 Condition 接口的类,进行更复杂的条件判断。

二、操作步骤(非常详细)

步骤 1:理解 @Conditional 接口(底层原理)

所有条件注解最终都依赖于 @Conditional。它接受一个或多个 Condition 接口的实现。

  • Condition 接口:
    public interface Condition {
        boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    }
    
    • matches 方法返回 true 表示条件满足,对应的配置类或 Bean 将被加载/创建。
    • ConditionContext: 提供了访问 BeanFactory, Environment, ResourceLoader, ClassLoader 等上下文信息。
    • AnnotatedTypeMetadata: 提供了访问注解元数据(如注解的属性值)的能力。

步骤 2:使用 @ConditionalOnClass(最常用)

场景: 只有在类路径中存在 DataSource 类时,才加载 DataSourceAutoConfiguration

  1. 创建配置类(或自动配置类):

    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import javax.sql.DataSource;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration(proxyBeanMethods = false) // 标记为配置类
    @ConditionalOnClass(DataSource.class) // 核心条件注解
    public class MyDataSourceConfiguration {
    
        // 这个配置类中的所有 @Bean 方法,只有在 DataSource 类存在时才会被处理
        // ...
    }
    
    • @Configuration(proxyBeanMethods = false): proxyBeanMethods=false 是 Spring Boot 2.2+ 推荐的,用于提升性能,表示不代理 Bean 方法(即不处理 @Bean 方法间的调用),因为自动配置通常不需要。
    • @ConditionalOnClass(DataSource.class): 指定条件。DataSourcejavax.sql.DataSource
  2. 指定多个类:

    @ConditionalOnClass({DataSource.class, JdbcTemplate.class})
    public class MyDataConfiguration {
        // 当 DataSource 和 JdbcTemplate 都存在时才生效
    }
    
  3. 使用 name 属性(避免编译时依赖):

    @ConditionalOnClass(name = "com.example.MyService")
    public class MyServiceConfiguration {
        // 当类路径中存在名为 "com.example.MyService" 的类时生效
        // 优点:不需要在编译时将 MyService 加入依赖,只在运行时检查。
    }
    
    • 重要区别: 使用 value (如 DataSource.class) 需要在编译时有该类的依赖。使用 name 属性则不需要,只在运行时通过类加载器检查。

步骤 3:使用 @ConditionalOnMissingBean(防止 Bean 冲突)

场景: 只有在用户没有自己定义 MyService Bean 时,才创建一个默认的 MyService Bean。

  1. 在自动配置类中定义 Bean:

    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(MyService.class) // 确保 MyService 类存在
    public class MyServiceAutoConfiguration {
    
        @Bean
        @ConditionalOnMissingBean // 核心条件
        public MyService myService() {
            return new DefaultMyService(); // 返回一个默认实现
        }
    }
    
    • @ConditionalOnMissingBean 放在 @Bean 方法上,作用于该方法创建的 Bean。
    • Spring 容器在创建 myService Bean 之前,会检查当前容器中是否已经存在类型为 MyService 的 Bean。
    • 如果不存在,则创建 DefaultMyService 实例。
    • 如果存在(例如,用户在自己的 @Configuration 类中定义了 @Bean MyService myCustomService()),则跳过 myService() 方法,不创建默认 Bean。
  2. 更精确的匹配(指定名称、类型、搜索范围):

    @Bean
    @ConditionalOnMissingBean(
        value = MyService.class, // 指定 Bean 类型
        name = "customMyService", // 指定 Bean 名称(可选)
        search = SearchStrategy.CURRENT // 搜索范围:仅在当前配置类中搜索(默认是 ALL)
    )
    public MyService myService() {
        return new DefaultMyService();
    }
    
    • value: Bean 的类型。
    • name: Bean 的名称(@Bean 注解的 name 属性或方法名)。
    • parameterizedContainer: 用于泛型容器(如 List, Map)。
    • search: 搜索策略 (ALL, CURRENT, ANCESTORS)。

步骤 4:组合使用多个条件注解

场景: 只有在类路径有 RedisConnectionFactory 且容器中没有 RedisTemplate Bean 时,才创建默认的 RedisTemplate

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class) // 类路径有 Redis 连接工厂
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean // 容器中没有 RedisTemplate
    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<?, ?> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // ... 其他配置
        return template;
    }
}
  • 这里 @ConditionalOnClass 作用于整个配置类,@ConditionalOnMissingBean 作用于具体的 @Bean 方法。
  • 条件是“与”(AND)关系:两个条件都必须满足。

步骤 5:使用 @ConditionalOnProperty

场景: 只有当配置文件中 app.feature.enabled=true 时,才启用某个功能。

  1. application.propertiesapplication.yml 中设置:

    # application.properties
    app.feature.enabled=true
    
    # application.yml
    app:
      feature:
        enabled: true
    
  2. 在配置类或 Bean 方法上使用:

    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration(proxyBeanMethods = false)
    public class FeatureConfiguration {
    
        @Bean
        @ConditionalOnProperty(
            name = "app.feature.enabled", // 配置属性的名称
            havingValue = "true",         // 期望的值(可选,默认为 "true")
            matchIfMissing = false       // 如果属性不存在,条件是否成立?(可选,默认 false)
        )
        public FeatureService featureService() {
            return new FeatureServiceImpl();
        }
    }
    
    • name: 必需,属性名。
    • havingValue: 可选,期望的值。如果省略,只要属性存在且值不为 false (忽略大小写) 就成立。
    • matchIfMissing: 可选,如果属性在配置文件中不存在,条件是否成立?默认 false

三、常见错误

  1. @ConditionalOnMissingBean 作用范围错误:

    • 错误:@ConditionalOnMissingBean 放在配置类上,期望控制类中所有 Bean。
    • 正确: @ConditionalOnMissingBean 必须放在 @Bean 方法上,它只对该方法创建的 Bean 生效。要控制整个配置类,应使用 @ConditionalOnBean@ConditionalOnMissingBeanvalue/name 属性结合 @Configuration,但这不常见且复杂,通常用其他条件注解组合。
  2. @ConditionalOnClass 的编译依赖问题:

    • 错误: 在自动配置模块中直接使用 @ConditionalOnClass(SomeExternalClass.class),但 SomeExternalClass 来自一个可选依赖(Optional Dependency),导致编译失败。
    • 正确: 使用 @ConditionalOnClass(name = "com.external.SomeExternalClass") 避免编译时依赖。或者,将可选依赖声明为 providedoptional
  3. 条件检查时机与 Bean 创建顺序:

    • 错误: 认为 @ConditionalOnBean 能检查到所有 Bean,包括那些在后续配置中定义的 Bean。
    • 正确: 条件检查发生在配置类处理阶段,早于大部分 Bean 的实际创建。@ConditionalOnBean 通常只能检测到之前已经注册的 Bean(如通过 @Import 或更早的自动配置)。@ConditionalOnMissingBean 更安全,因为它通常用于提供默认实现。
  4. @ConditionalOnPropertyhavingValue 匹配:

    • 错误: 设置 havingValue = "on",但在配置中写 app.feature.enabled=true,导致不匹配。
    • 正确: 确保 havingValue 的值与配置文件中的值完全匹配(字符串比较),或省略 havingValue 让其默认匹配非 false 值。
  5. 忽略 proxyBeanMethods = false

    • 错误: 在自动配置类上使用 @Configuration 而不设置 proxyBeanMethods = false
    • 正确: 除非配置类内部的 @Bean 方法有相互调用且需要代理(如 @Transactional),否则应设置 proxyBeanMethods = false 以提升启动性能。

四、注意事项

  1. 顺序很重要: Spring Boot 自动配置的加载顺序由 @AutoConfigureOrder, @AutoConfigureBefore, @AutoConfigureAfter 控制。确保你的条件逻辑不依赖于未加载的配置。
  2. @ConditionalOnMissingBean 的宽松性: 它检查的是类型和/或名称。即使用户定义的 Bean 是子类或不同实现,只要类型匹配,它就会阻止默认 Bean 的创建。使用 search 属性可以限制搜索范围。
  3. @ConditionalOnClass 的局限性: 它只检查类路径,不保证类能被正确实例化或有正确的依赖。它只是一个初步的“门卫”。
  4. 避免过度使用 @ConditionalOnExpression SpEL 表达式可能使配置变得复杂且难以调试。优先使用专门的条件注解。
  5. 测试: 为使用了条件注解的配置编写测试,模拟不同条件(如类存在/不存在,属性设置/未设置)来验证行为。

五、使用技巧

  1. 组合条件: 灵活组合多个 @Conditional 注解(或其衍生注解)来实现复杂逻辑。它们是“与”关系。
  2. 利用 name 属性解耦: 在自动配置模块中,优先使用 @ConditionalOnClass(name = "...")@ConditionalOnMissingClass(name = "...") 来避免对可选库的编译时硬依赖。
  3. 提供合理的默认值: @ConditionalOnPropertymatchIfMissing = true 可以用于启用某些默认功能,除非用户明确禁用。
  4. 日志记录: 在复杂的 Condition 实现中(自定义 @Conditional),添加日志有助于调试条件判断过程。
  5. 利用 IDE 提示: 现代 IDE (如 IntelliJ IDEA) 通常能识别 Spring Boot 条件注解,并在代码中给出提示或警告。

六、最佳实践

  1. 优先使用 @ConditionalOnMissingBean 在提供自动配置时,这是防止与用户自定义 Bean 冲突的黄金标准
  2. 明确的条件: 使用最具体的条件注解。例如,用 @ConditionalOnWebApplication 而不是 @ConditionalOnClass(name = "org.springframework.web.context.support.GenericWebApplicationContext")
  3. 模块化配置: 将相关的自动配置放在独立的配置类中,并用适当的条件注解标记。
  4. 文档化: 在自动配置类或文档中说明其生效的条件。
  5. 遵循 Spring Boot 命名约定: 自动配置类通常命名为 *AutoConfiguration
  6. proxyBeanMethods = false 对于不涉及内部 @Bean 方法调用代理的配置类,务必设置此属性以优化性能。

七、性能优化

  1. proxyBeanMethods = false 这是最直接的性能优化。它避免了为配置类创建 CGLIB 代理,减少了启动时间和内存占用。这是 Spring Boot 自动配置的最佳实践。
  2. 尽早排除: 使用 @ConditionalOnClass, @ConditionalOnProperty 等注解在配置类加载的早期就排除掉不需要的配置,避免解析和处理其中的 @Bean 方法。
  3. 避免复杂的 Condition 实现: 自定义 Conditionmatches 方法应尽量轻量,避免进行耗时的 I/O 操作或复杂计算。
  4. 合理使用 @ConditionalOnBean 尽量避免依赖 @ConditionalOnBean 来检查在后续阶段才会注册的 Bean,这可能导致不必要的配置类被加载和检查。优先使用 @ConditionalOnMissingBean 或其他前置条件。
  5. 启用启动分析: 使用 Spring Boot Actuator 的 /startup 端点(如果可用)或启动日志来分析哪些自动配置被应用/排除,找出潜在的优化点。

总结: Spring Boot 的条件注解是构建灵活、智能自动配置的基石。熟练掌握 @ConditionalOnClass@ConditionalOnMissingBean 是关键。遵循最佳实践,特别是 proxyBeanMethods = false,能有效提升应用性能。通过组合使用这些注解,可以创建出既强大又健壮的配置。