Spring Boot 的条件装配注解有哪些?如何自定义 @Conditional?
简化版
条件装配是 Spring Boot 自动配置「智能、按需、可覆盖」的核心机制——一个 Bean/配置满足特定条件时才生效。核心注解都以 @ConditionalOnXxx 开头:@ConditionalOnClass(classpath 有某个类才生效,如「有 Tomcat 类才配 Tomcat」)、@ConditionalOnMissingBean(容器里没有某个 Bean 才生效,是「用户可覆盖默认」的关键)、@ConditionalOnProperty(配置项满足才生效)、@ConditionalOnBean(有某 Bean 才生效)等。它们都基于底层的 @Conditional + Condition 接口——想自定义就写一个实现 Condition 接口的类,在 matches() 里返回 true/false 决定是否装配。
详细版
常用条件注解:
| 注解 | 生效条件 |
|---|---|
@ConditionalOnClass | classpath 存在指定的类 |
@ConditionalOnMissingClass | classpath 不存在指定的类 |
@ConditionalOnBean | 容器中存在指定的 Bean |
@ConditionalOnMissingBean | 容器中不存在指定的 Bean(用户可覆盖默认) |
@ConditionalOnProperty | 配置项存在且符合期望值 |
@ConditionalOnWebApplication | 当前是 Web 应用 |
@ConditionalOnExpression | SpEL 表达式为 true |
@ConditionalOnResource | 存在指定的资源文件 |
自动配置里的典型用法:
@Configuration
@ConditionalOnClass(DataSource.class) // classpath 有 DataSource 类才配数据源
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 你没自己定义 DataSource 才用这个默认的
public DataSource dataSource() {
return new HikariDataSource();
}
}
自定义 @Conditional:
// 1. 实现 Condition 接口
public class OnLinuxCondition implements Condition {
public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
}
// 2. 用 @Conditional 引用它
@Bean
@Conditional(OnLinuxCondition.class) // 只在 Linux 上才创建这个 Bean
public FileMonitor fileMonitor() { return new LinuxFileMonitor(); }
⚠️
@ConditionalOnMissingBean是「用户覆盖默认配置」的关键,但它对执行顺序敏感:自动配置类要在你的配置之后加载,@ConditionalOnMissingBean才能正确判断「用户有没有自己定义」。Spring Boot 保证自动配置最后加载(@AutoConfiguration的排序机制),所以你的自定义 Bean 总能覆盖默认——但如果你自己写的两个配置类互相用@ConditionalOnBean/@ConditionalOnMissingBean,顺序不当会出现「谁都没生效」的诡异问题。
完整版教学
一、条件装配解决什么:让自动配置「聪明」
Spring Boot 的自动配置候选有几百个(数据库、缓存、消息队列、Web…),但一个应用不可能全用上。如果无脑全装,会报一堆错(没引数据库依赖却装数据源,直接崩)。条件装配让每个自动配置「先判断条件、满足才生效」:
无条件装配:把所有候选配置都装上 → 没引 Redis 却配 Redis → 连不上报错
条件装配: 每个配置有 @ConditionalOnXxx 条件
DataSourceAutoConfiguration:@ConditionalOnClass(DataSource.class)
→ 你引了 JDBC 依赖(classpath 有 DataSource)才装,否则跳过
→ 引什么依赖装什么,没引的不装,智能又干净
所以条件装配是自动配置「智能、按需」的技术核心——它让「几百个候选配置」在你的具体项目里,只有相关的那些生效。理解「自动配置 = 一堆带条件的配置类」,就理解了 Spring Boot「引入 starter 就自动配好」的原理:starter 带来依赖 → 依赖让某些 @ConditionalOnClass 满足 → 对应配置生效。
二、@ConditionalOnClass:按依赖存在与否装配
@ConditionalOnClass 是自动配置里用得最多的——「classpath 里有某个类,才启用这个配置」:
@Configuration
@ConditionalOnClass(RedisOperations.class) // 有 Redis 相关的类才配
public class RedisAutoConfiguration { ... }
它的巧妙之处在于:类是否存在,间接反映了「你有没有引入对应的依赖」。你引入 spring-boot-starter-data-redis,classpath 就有了 RedisOperations 类,@ConditionalOnClass 满足,Redis 自动配置生效。你没引,就没这个类,配置跳过。
引入 starter → 带来 jar 依赖 → classpath 出现特定类 → @ConditionalOnClass 满足 → 配置生效
这条链就是"引入 starter 即自动配置"的完整逻辑
一个反直觉点:@ConditionalOnClass 里引用一个「可能不存在的类」,为什么不报 ClassNotFoundException?因为 Spring 用字节码分析(ASM) 读注解,不会真正加载那个类,所以类不存在也不会报错,只是条件不满足而已。这是它能「安全地判断类是否存在」的原因。
三、@ConditionalOnMissingBean:用户覆盖默认的钥匙
这是最需要理解的一个——它是「Spring Boot 提供默认,但允许你覆盖」的实现关键:
@Bean
@ConditionalOnMissingBean(DataSource.class) // 容器里没有 DataSource 才用这个
public DataSource dataSource() {
return new HikariDataSource(); // Spring Boot 的默认数据源
}
逻辑:「如果你没自己定义 DataSource,我就给你一个默认的;如果你自己定义了,就用你的、我不插手」。这就是「约定优于配置 + 允许覆盖」的精髓——大多数情况用默认(省心),需要定制时你自己定义一个同类型 Bean 就自动覆盖默认。
你没定义 DataSource → @ConditionalOnMissingBean 满足 → 用 Spring Boot 默认的
你自己 @Bean 定义了 DataSource → 容器已有 → @ConditionalOnMissingBean 不满足 → 默认的不生效,用你的
关键前提:自动配置必须在你的配置之后加载,@ConditionalOnMissingBean 判断时你的 Bean 已经注册了,才能正确「让路」。Spring Boot 通过让自动配置类排在最后加载来保证这一点。这是「为什么我自己定义的 Bean 能覆盖 Spring Boot 默认」的底层答案。
四、@ConditionalOnProperty:用配置开关控制装配
@ConditionalOnProperty 让你用配置文件里的开关控制 Bean 是否装配,非常实用:
@Bean
@ConditionalOnProperty(
prefix = "myapp.cache",
name = "enabled",
havingValue = "true", // 配置值等于 true 才生效
matchIfMissing = false) // 配置不存在时的默认行为(这里是不生效)
public CacheManager cacheManager() { ... }
# application.yml
myapp:
cache:
enabled: true # 改成 false 就不装配 cacheManager
用途:功能开关(feature toggle)——通过改配置就能启用/禁用某个功能模块,不用改代码。matchIfMissing 很关键:它决定「配置项没写时」是否生效——true 表示「默认开启」(没配也生效),false 表示「默认关闭」(没配就不生效)。很多 Spring Boot 内部配置用 matchIfMissing = true(默认开启,你想关才配 false)。这是「配置驱动装配」的标准手段。
五、自定义 @Conditional:底层原理
所有 @ConditionalOnXxx 都是基于底层的 @Conditional + Condition 接口。想自定义条件,就实现 Condition 接口:
// Condition 接口只有一个方法:返回 true 才装配
public class OnProductionCondition implements Condition {
@Override
public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {
// ctx 能拿到环境、BeanFactory、classloader 等,据此判断
String env = ctx.getEnvironment().getProperty("app.env");
return "prod".equals(env); // 只在生产环境返回 true
}
}
// 用 @Conditional 挂上它
@Bean
@Conditional(OnProductionCondition.class) // 只在生产环境创建
public AlertService alertService() { return new RealAlertService(); }
核心机制:Spring 在注册 Bean 前,会调用 Condition.matches(),返回 false 就跳过这个 Bean。ConditionContext 提供了判断所需的一切(环境变量、已注册的 Bean、classpath 等)。实际上 @ConditionalOnClass、@ConditionalOnProperty 这些内置注解,底层都是「一个 @Conditional + 一个对应的 Condition 实现」的封装。理解这一层,就能写出任意自定义条件(如「只在某数据库版本下」「只在某个 feature flag 开启时」装配 Bean)。
六、条件的组合与执行顺序陷阱
多个条件可以叠加(都满足才生效),也可以有顺序依赖:
@Bean
@ConditionalOnClass(RedisOperations.class) // 且 classpath 有 Redis
@ConditionalOnProperty(name = "cache.type", havingValue = "redis") // 且配置指定用 redis
@ConditionalOnMissingBean // 且用户没自己定义
public CacheManager redisCacheManager() { ... }
// 三个条件全满足才装配
一个必须注意的顺序陷阱:@ConditionalOnBean 和 @ConditionalOnMissingBean 依赖「判断时容器里 Bean 的状态」,而 Bean 是按顺序注册的:
配置类 A:@Bean @ConditionalOnMissingBean B ... // 若 B 还没注册,条件满足,A 生效
配置类 B:@Bean ... // B 后注册
问题:如果 A 先加载,判断时 B 还没注册 → A 以为"没有 B"就生效了 → 但其实 B 后面会注册
→ 顺序不当导致结果不符预期
所以 @ConditionalOnBean/@ConditionalOnMissingBean 对加载顺序敏感,一般只用在自动配置类里(Spring Boot 保证自动配置最后加载)。你自己的业务配置类之间别随意用它们互相判断,否则会踩「顺序导致条件误判」的坑。
记忆钩子:「@ConditionalOnXxx 让自动配置按需生效:OnClass(有依赖类才配,ASM 分析不报错)、OnMissingBean(你没定义才用默认,是覆盖的关键、对顺序敏感)、OnProperty(配置开关控制、matchIfMissing 定默认);底层是 @Conditional + Condition.matches(),自定义就实现 Condition 接口」。
七、常见误区与追问
- 误区:@ConditionalOnClass 引用不存在的类会报 ClassNotFoundException。 不会,Spring 用 ASM 字节码分析读注解、不真正加载类,类不存在只是条件不满足,不报错。
- 误区:Spring Boot 的默认 Bean 无法覆盖。 能覆盖——默认 Bean 上有 @ConditionalOnMissingBean,你自己定义同类型 Bean 时默认的就不生效,用你的。
- 误区:@ConditionalOnMissingBean 的判断和加载顺序无关。 高度相关——必须自动配置在你的配置之后加载,才能正确判断「用户有没有定义」;自己的配置类间乱用会因顺序误判。
- 误区:条件装配是 Spring Boot 独有的。 @Conditional 是 Spring 4 就有的核心机制;@ConditionalOnXxx 是 Spring Boot 在其上封装的一系列便捷注解。
- 追问:@ConditionalOnMissingBean 为什么能实现「用户覆盖默认」? 默认 Bean 加了它,只有容器里没有同类型 Bean 时才生效;你自己定义了同类型 Bean(且先注册),条件不满足,默认的让路,实现覆盖。
- 追问:怎么自定义一个条件注解? 实现 Condition 接口的 matches 方法(返回 true 才装配),用 @Conditional(你的Condition.class) 挂到 @Bean/@Configuration 上;也可再封装成自定义注解。
- 追问:matchIfMissing 是什么? @ConditionalOnProperty 的属性,决定「配置项不存在时」条件是否满足——true 表示默认生效(没配也装)、false 表示默认不生效(没配不装)。
八、加强记忆
条件装配是 Spring Boot 自动配置「智能、按需、可覆盖」的核心——每个配置/Bean 加 @ConditionalOnXxx,满足条件才生效,让几百个候选配置在你的项目里只装相关的那些。核心注解:@ConditionalOnClass(classpath 有某类才配,用 ASM 分析所以引用不存在的类也不报错,是「引 starter 即自动配置」的关键——依赖带来类、类满足条件);@ConditionalOnMissingBean(容器没有某 Bean 才用默认,是「用户覆盖默认」的钥匙,但对加载顺序敏感,靠 Spring Boot 保证自动配置最后加载);@ConditionalOnProperty(配置开关控制装配,matchIfMissing 定「没配时」的默认行为,做 feature toggle);还有 OnBean/OnWebApplication/OnExpression 等。它们底层都是 @Conditional + Condition 接口(Spring 4 就有),自定义就实现 Condition.matches() 返回 true/false。多条件可叠加,但 OnBean/OnMissingBean 别在自己的业务配置间乱用(顺序会导致误判)。一句话「@ConditionalOnXxx 按需装配:OnClass 看依赖、OnMissingBean 让用户覆盖、OnProperty 配置开关,底层 Condition.matches 决定生效」。