@Conditional 系列注解 详解及详细源码展示

@Conditional 系列注解是 Spring 框架中实现条件化 Bean 注册配置类生效的核心工具,广泛用于 Spring Boot 的自动配置机制中。它们通过判断特定条件(如类路径存在性、Bean 是否已存在、环境属性值等)决定是否加载某个 Bean 或配置类,从而避免冗余配置并提升应用的灵活性。以下从注解分类、源码解析、核心功能、使用场景源码实现细节展开详细说明。


一、@Conditional 系列注解分类

Spring 提供了多个 @Conditional 系列注解,按功能可分为以下几类:

注解作用典型场景
@ConditionalOnClass当类路径中存在指定类时生效数据库驱动存在时自动配置 DataSource
@ConditionalOnMissingBean当容器中不存在指定类型的 Bean 时生效避免覆盖用户自定义的 DataSource Bean。
@ConditionalOnProperty当配置文件中指定属性满足条件时生效根据 application.properties 中的 env=prod 启用生产环境配置。
@ConditionalOnWebApplication当应用是 Web 应用时生效仅在 Web 环境中配置 DispatcherServlet
@ConditionalOnNotWebApplication当应用不是 Web 应用时生效仅在非 Web 环境中配置 CommandLineRunner
@ConditionalOnResource当类路径中存在指定资源文件时生效仅当 META-INF/beans.xml 存在时加载传统 XML 配置。
@ConditionalOnExpression当 SpEL 表达式计算结果为 true 时生效自定义复杂条件(如 ${my.property} > 100)。

二、核心注解详解与源码解析

1. @ConditionalOnClass:类路径存在性检查

@ConditionalOnClass 用于判断类路径中是否存在指定的类(通过类加载器加载),若存在则生效。

源码定义(Spring Framework 6.1):
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    /**
     * 需要检查的类(支持多个)
     */
    Class<?>[] value() default {};

    /**
     * 类名的字符串形式(支持通配符,如 "com.example.*Service")
     */
    String[] names() default {};
}

关键逻辑
通过 OnClassCondition 类实现条件评估,核心方法是 matches,用于检查类路径中是否存在目标类:

// OnClassCondition 核心方法(简化)
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // 获取 @ConditionalOnClass 注解的 value 或 names 属性
    Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
    Class<?>[] classes = (Class<?>[]) attributes.get("value");
    String[] classNames = (String[]) attributes.get("names");

    // 检查类路径中是否存在任意一个类
    for (Class<?> clazz : classes) {
        if (ClassUtils.isPresent(clazz.getName(), context.getClassLoader())) {
            return true;
        }
    }
    for (String className : classNames) {
        if (ClassUtils.isPresent(className, context.getClassLoader())) {
            return true;
        }
    }
    return false;
}

使用示例
仅在类路径存在 DataSource 时生效(常见于数据库自动配置):

@Configuration
@ConditionalOnClass(DataSource.class) // 仅当类路径有 DataSource 时生效
public class DataSourceAutoConfiguration {
    // 自动配置 DataSource...
}

2. @ConditionalOnMissingBean:Bean 不存在性检查

@ConditionalOnMissingBean 用于判断 Spring 容器中是否不存在指定类型的 Bean(或其别名),若不存在则生效。常用于覆盖默认自动配置,允许用户自定义 Bean。

源码定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMissingBeanCondition.class)
public @interface ConditionalOnMissingBean {
    /**
     * 需要检查的 Bean 类型(支持多个)
     */
    Class<?>[] value() default {};

    /**
     * Bean 的名称(支持 SpEL 表达式)
     */
    String[] name() default {};

    /**
     * 是否考虑父容器中的 Bean(默认 false)
     */
    boolean considerParent() default false;
}

关键逻辑
通过 OnMissingBeanCondition 评估容器中是否存在目标 Bean。若用户已定义同名或同类型的 Bean,则自动配置类不会生效。

使用示例
用户自定义 DataSource 时,自动跳过 Spring Boot 默认的数据源配置:

@Configuration
public class MyDataSourceConfig {
    @Bean
    public DataSource myDataSource() {
        // 用户自定义数据源
    }
}

// Spring Boot 默认数据源配置(仅当用户未定义 DataSource 时生效)
@Configuration
@ConditionalOnMissingBean(DataSource.class) // 用户无 DataSource 时生效
public class DataSourceAutoConfiguration {
    @Bean
    public DataSource defaultDataSource() {
        // 默认数据源配置
    }
}

3. @ConditionalOnProperty:配置属性检查

@ConditionalOnProperty 用于判断配置文件中指定属性的值是否满足条件(如等于某个值、存在等),若满足则生效。常用于根据环境动态启用功能。

源码定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    /**
     * 配置属性的名称(支持 SpEL 表达式)
     */
    String[] name();

    /**
     * 期望的属性值(可选,若不指定则仅检查属性存在)
     */
    String[] havingValue() default {};

    /**
     * 是否忽略属性不存在的情况(默认 false,即属性不存在时条件不满足)
     */
    boolean matchIfMissing() default false;
}

关键逻辑
通过 OnPropertyCondition 读取配置文件中的属性值,与 havingValue 比较(或仅检查存在性)。

使用示例
仅当 application.propertiesenv=prod 时启用生产环境监控:

@Configuration
@ConditionalOnProperty(
    name = "env", 
    havingValue = "prod", 
    matchIfMissing = false // 属性不存在时不生效
)
public class ProductionMonitorAutoConfiguration {
    @Bean
    public MonitorService monitorService() {
        return new ProductionMonitor();
    }
}

4. @ConditionalOnWebApplication:Web 应用检查

@ConditionalOnWebApplication 用于判断当前应用是否为 Web 应用(通过 ServletContext 是否存在),若为 Web 应用则生效。常用于区分 Web 和非 Web 环境的配置。

源码定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
    /**
     * 是否严格检查(默认 true,要求是完整的 Web 应用)
     */
    boolean strict() default true;
}

关键逻辑
通过 OnWebApplicationCondition 检查 ServletContext 是否存在(strict=true 时要求是完整的 Web 应用,strict=false 时允许 Servlet 环境)。

使用示例
仅在 Web 应用中配置 DispatcherServlet

@Configuration
@ConditionalOnWebApplication // 仅当是 Web 应用时生效
public class WebMvcAutoConfiguration {
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }
}

5. @ConditionalOnExpression:SpEL 表达式检查

@ConditionalOnExpression 允许使用 SpEL(Spring 表达式语言)自定义复杂条件,若表达式结果为 true 则生效。适用于需要动态计算的场景。

源码定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {
    /**
     * SpEL 表达式(结果为 true 时生效)
     */
    String value();
}

关键逻辑
通过 OnExpressionCondition 解析并计算 SpEL 表达式,判断结果是否为 true

使用示例
仅当 my.property 大于 100 时启用高级功能:

@Configuration
@ConditionalOnExpression("${my.property} > 100") // SpEL 表达式
public class AdvancedFeatureAutoConfiguration {
    @Bean
    public AdvancedService advancedService() {
        return new AdvancedService();
    }
}

三、@Conditional 系列注解的协同工作流程

在 Spring Boot 自动配置中,@Conditional 系列注解通常与 @EnableAutoConfigurationAutoConfigurationImportSelector 协同工作,流程如下:

  1. 自动配置类加载AutoConfigurationImportSelectorMETA-INF/spring.factories 中加载所有 EnableAutoConfiguration 类型的自动配置类。
  2. 条件评估:对每个自动配置类,使用 ConditionEvaluator 评估其上的 @Conditional 注解(如 @ConditionalOnClass@ConditionalOnMissingBean)。
  3. 生效类导入:仅条件满足的自动配置类会被导入到 Spring 容器中,生成 BeanDefinition 并注册。

四、典型使用场景与最佳实践

1. 避免重复 Bean 注册

通过 @ConditionalOnMissingBean 允许用户自定义 Bean,覆盖默认自动配置:

@Configuration
public class UserConfig {
    @Bean
    public UserDetailsService userDetailsService() {
        // 用户自定义的用户详情服务
    }
}

// Spring Boot 默认的用户详情服务(仅当用户未定义时生效)
@Configuration
@ConditionalOnMissingBean(UserDetailsService.class)
public class DefaultUserDetailsServiceConfig {
    @Bean
    public UserDetailsService defaultUserDetailsService() {
        return new InMemoryUserDetailsManager();
    }
}

2. 环境差异化配置

通过 @ConditionalOnProperty 根据环境启用不同功能:

@Configuration
public class LogConfig {
    @Bean
    @ConditionalOnProperty(name = "log.level", havingValue = "DEBUG")
    public Logger debugLogger() {
        return LoggerFactory.getLogger("DEBUG_LOGGER");
    }

    @Bean
    @ConditionalOnProperty(name = "log.level", havingValue = "INFO", matchIfMissing = true)
    public Logger infoLogger() {
        return LoggerFactory.getLogger("INFO_LOGGER");
    }
}

3. 类路径依赖检查

通过 @ConditionalOnClass 确保依赖存在时才配置:

@Configuration
@ConditionalOnClass(RedisTemplate.class) // 仅当 RedisTemplate 存在时生效
public class RedisCacheConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.create(connectionFactory);
    }
}

4. Web 环境专属配置

通过 @ConditionalOnWebApplication 区分 Web 和非 Web 环境:

@Configuration
public class WebConfig {
    @Bean
    @ConditionalOnWebApplication // 仅 Web 环境生效
    public WebMvcConfigurer webMvcConfigurer() {
        return new MyWebMvcConfigurer();
    }
}

@Configuration
public class NonWebConfig {
    @Bean
    @ConditionalOnNotWebApplication // 仅非 Web 环境生效
    public CommandLineRunner commandLineRunner() {
        return args -> System.out.println("非 Web 环境启动");
    }
}

五、源码实现细节与注意事项

1. 条件评估的顺序

Spring 会按以下顺序评估 @Conditional 注解的条件:

  1. @ConditionalOnClass
  2. @ConditionalOnMissingBean
  3. @ConditionalOnProperty
  4. @ConditionalOnWebApplication
  5. @ConditionalOnExpression

2. 类加载器的作用

@ConditionalOnClass@ConditionalOnMissingBean 等注解的条件评估依赖 ClassLoader,需确保类加载器能正确加载目标类(如自定义类加载器可能导致条件判断失败)。

3. SpEL 表达式的安全性

@ConditionalOnExpression 使用 SpEL 表达式时,需避免使用危险操作(如 T(java.lang.Runtime).getRuntime().exec(...)),防止代码注入攻击。

4. 条件缓存的优化

Spring 会对条件评估结果进行缓存,避免重复计算。若条件依赖的属性或类动态变化(如通过 @RefreshScope 刷新配置),需手动清除缓存。


六、总结

@Conditional 系列注解是 Spring Boot 自动配置的“开关”,通过灵活的条件判断控制 Bean 和配置类的生效逻辑。掌握这些注解的使用(如 @ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty),可以帮助开发者:

  • 编写更健壮的自动配置类,避免冗余。
  • 实现环境差异化配置,提升应用的可维护性。
  • 精准控制 Bean 的注册,避免冲突。

理解其源码和评估逻辑,有助于解决自动配置不生效、Bean 重复注册等常见问题,是 Spring Boot 开发的高级必备技能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值