Spring Boot 能做到开箱即用,靠的不仅仅是自动装配,还有一套精细的条件装配机制。当你在 pom 里引入某个 Starter,相关的配置类是否生效,并不是启动时全部加载,而是由一个个条件注解来决定。@Conditional 就是这套机制的核心,它衍生出的 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等注解,构成了 Spring Boot 自动配置的判断基础。这篇文章把这套机制拆开讲清楚,并带你手写一个自定义条件注解。

一、条件装配的底层原理:Condition 接口如何工作
所有条件注解的源头都是 @Conditional,它的定义非常简单,只有一个 value 属性,接收实现了 Condition 接口的类。Spring 在注册 Bean 定义之前,会先调用 Condition 的 matches 方法,返回 true 才继续注册,返回 false 则直接跳过这个配置类或 Bean 方法。
关键的执行入口在 ConditionEvaluator 中,它由 ConfigurationClassParser 调用。也就是说,条件判断发生在配置类解析阶段,比 Bean 实例化要早得多。这一点很重要:条件注解里拿不到已经创建好的 Bean 实例,只能拿到 ConditionContext 提供的元信息,包括环境变量、BeanFactory、类加载器等。
public class OnLinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 通过环境信息判断当前操作系统
String os = context.getEnvironment()
.getProperty("os.name", "").toLowerCase();
return os.contains("linux");
}
}
@Configuration
@Conditional(OnLinuxCondition.class)
public class LinuxOnlyConfig {
@Bean
public TaskScheduler linuxScheduler() {
return new TaskScheduler();
}
}还有一个容易被忽略的细节:Condition 分为普通判断和配置类判断两类,即 Condition 与 ConfigurationCondition。后者的 getConfigurationPhase 方法可以指定判断发生在解析阶段还是注册阶段。Spring Boot 内置的条件注解大多通过 OnClassCondition、OnBeanCondition 等类实现,其中类路径判断必须发生在解析阶段,因为如果类都不存在,配置类根本无法完成加载。
二、常用条件注解的场景与区别
Spring Boot 在 @Conditional 基础上派生了十几个注解,各自对应不同的判断维度,理解它们各自的适用场景是用好条件装配的前提。
@ConditionalOnClass 判断类路径是否存在某个类,这是 Starter 最常用的方式。比如 spring-boot-starter-data-redis 的自动配置类上有 @ConditionalOnClass(RedisOperations.class),只有引入了 Redis 客户端依赖,这份配置才会生效。需要注意的是,被判断的类如果写在方法返回值或字段类型上,一旦类不存在会直接抛 ClassNotFoundError,所以推荐把类名写成字符串形式 name = "com.xxx.CacheClient",或者用全字符串配置,避免字节码层面的强依赖。
@ConditionalOnMissingBean 是实现用户配置覆盖默认配置的关键。自动配置类中定义默认 Bean 时加上它,一旦用户自己声明了同类型 Bean,默认的就让位。配合 search 属性还能控制搜索范围,默认 SearchStrategy.ALL 会搜索所有祖先容器,在父子容器场景(如传统的 Spring MVC + Spring Boot 组合)要特别小心误判。
@Configuration
public class CacheAutoConfiguration {
@Bean
// 用户没有自己定义 CacheManager 时才注册默认实现
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager defaultCacheManager() {
return new ConcurrentMapCacheManager();
}
@Bean
// 配置文件中 cache.type=redis 且值存在时才生效
@ConditionalOnProperty(name = "cache.type", havingValue = "redis")
public RedisCacheManager redisCacheManager() {
return new RedisCacheManager();
}
}@ConditionalOnProperty 通过配置文件做开关,是最灵活的一个。它的 matchIfMissing 属性决定了配置项不存在时的行为,默认 false。写自定义 Starter 时,建议为关键功能提供 xxx.enabled 开关,并明确设置 matchIfMissing 的值,避免用户没配置时行为不明确。
其余注解也各有分工:@ConditionalOnBean 和 @ConditionalOnMissingBean 依赖 Bean 定义顺序,官方并不推荐在自动配置中大量使用;@ConditionalOnWebApplication 与 @ConditionalOnNotWebApplication 区分 Web 环境;@ConditionalOnExpression 支持 SpEL 表达式做复合判断;@ConditionalOnJava 可以限定 JDK 版本。实际开发中经常需要多个条件组合,直接叠加注解即可,它们之间是与的关系。
| 注解 | 判断依据 | 典型场景 |
|---|---|---|
| @ConditionalOnClass | 类路径是否存在某类 | Starter 自动配置守门 |
| @ConditionalOnMissingBean | 容器中是否缺少某 Bean | 提供可覆盖的默认实现 |
| @ConditionalOnProperty | 配置文件属性值 | 功能开关 |
| @ConditionalOnWebApplication | 是否为 Web 环境 | 环境相关 Bean 注册 |
| @ConditionalOnResource | 资源文件是否存在 | 依赖外部配置文件的组件 |
三、实战:手写一个自定义条件注解
如果内置注解不能满足需求,比如需要根据注册表键值或者某个外部服务状态来决定是否装配,就需要自定义。做法分三步:写 Condition 实现类、写组合注解、应用到配置类上。下面以常见的多数据源场景为例,根据配置文件中的 db.type 决定装配哪种数据源。
// 第一步:定义条件判断逻辑
public class DbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 从注解属性中拿到期望的数据库类型
Map<String, Object> attrs = metadata.getAnnotationAttributes(
ConditionalOnDbType.class.getName());
String expected = (String) attrs.get("value");
// 从环境变量或配置文件读取实际类型,未配置时默认 mysql
String actual = context.getEnvironment()
.getProperty("db.type", "mysql");
return expected.equalsIgnoreCase(actual);
}
}
// 第二步:定义组合注解,比直接用 @Conditional 更直观
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DbTypeCondition.class)
public @interface ConditionalOnDbType {
String value();
}
// 第三步:应用到配置类
@Configuration
public class DataSourceConfig {
@Bean
@ConditionalOnDbType("mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://127.0.0.1:3306/app")
.build();
}
@Bean
@ConditionalOnDbType("postgres")
public DataSource postgresDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://127.0.0.1:5432/app")
.build();
}
}写自定义条件时有几个坑要避开。第一,matches 方法中不要做耗时操作,它会在启动阶段被多次调用,拖慢启动速度。第二,注解属性通过 metadata.getAnnotationAttributes 获取时,返回的键是注解定义的属性名,不是随意命名的。第三,如果你的判断依赖其他 Bean 的注册情况,考虑实现 ConfigurationCondition 并把阶段设为 REGISTER_BEAN,否则可能因为时序问题得到不可预期的结果。
最后补充一个排查技巧:当条件装配结果不符合预期时,不要靠猜。在启动类上开启 debug 模式,也就是在配置文件中加上 debug=true,或在启动参数中加 --debug,启动日志会输出一份条件评估报告,明确列出哪些自动配置类因为哪个条件不满足而未生效,这是定位条件装配问题最直接的手段。配合 ConditionEvaluationReport 还可以在代码中拿到完整报告做二次分析。
总的来说,条件装配是 Spring Boot 自动配置的基石,理解 Condition 的执行时机、各条件注解的判断维度以及自定义扩展方式,不仅能让你看懂官方 Starter 的源码,也能在自己设计可插拔组件时游刃有余。
Spring Boot条件装配@Conditional修改时间:2026-09-14 04:40:40