一,快速入门
1.1 什么是 Spring 条件注解
在 Spring Framework 中,** 条件注解(Conditional Annotation)** 用于 根据运行时条件决定某个 Bean 是否被注册到 IoC 容器中。
本质机制:
只有当指定条件成立时,Spring 才会创建并注册该 Bean。
底层依赖核心接口:
org.springframework.context.annotation.Condition
Spring 在解析 @Configuration 或 @Bean 时,会调用 Condition 判断条件是否成立。
核心注解:
@Conditional
1.2 为什么需要条件注解
在实际工程中,经常会出现:
| 场景 | 需求 |
|---|---|
| 多环境部署 | dev / test / prod |
| 多数据库支持 | MySQL / PostgreSQL |
| 自动装配 | 有某个类才启用某组件 |
| 可选功能模块 | 开关控制 |
| Spring Boot Starter | 自动配置 |
例如:
如果项目中存在 Redis 依赖 -> 才加载 Redis 配置
如果配置文件开启 feature=true -> 才启用功能
1.3 条件注解的核心结构
Condition接口
public interface Condition {
boolean matches(
ConditionContext context,
AnnotatedTypeMetadata metadata
);
}
参数说明
| 参数 | 作用 |
|---|---|
| ConditionContext | Spring上下文 |
| Environment | 读取配置 |
| BeanFactory | Bean工厂 |
| ClassLoader | 类加载器 |
| AnnotatedTypeMetadata | 注解元数据 |
返回值:
true -> 注册Bean
false -> 不注册
1.4 Spring原生条件注解
在 Spring Core 中,只有一个:
@Conditional
但在 Spring Boot 中扩展出了大量条件注解(自动配置核心)。
常见如下:
| 注解 | 作用 |
|---|---|
| @ConditionalOnClass | 类存在才生效 |
| @ConditionalOnMissingBean | Bean不存在才创建 |
| @ConditionalOnBean | Bean存在才创建 |
| @ConditionalOnProperty | 配置存在 |
| @ConditionalOnExpression | SpEL表达式 |
| @ConditionalOnJava | Java版本 |
| @ConditionalOnResource | 资源存在 |
| @ConditionalOnWebApplication | Web环境 |
这些全部底层都是:
@Conditional
@ConditionalOnClass
作用:只有当 classpath 中存在指定类 时才加载配置。
常用于:Starter 自动配置
例如:只有存在 Redis 依赖才加载 Redis 配置。
Demo
@Configuration
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public class RedisAutoConfiguration {
@Bean
public RedisClient redisClient() {
return new RedisClient();
}
}
逻辑
classpath 存在 Jedis
↓
RedisAutoConfiguration 生效
↓
创建 redisClient
如果项目 没有引入 Redis 依赖:
该配置不会加载
@ConditionalOnMissingBean
作用:只有 Bean 不存在才创建
避免 自动配置覆盖用户自定义 Bean。
Demo
@Configuration
public class JacksonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
自定义
@Bean
public ObjectMapper objectMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
return mapper;
}
结果
用户定义 Bean
↓
自动配置不生效
这是 Spring Boot 扩展机制核心。
@ConditionalOnBean
作用: 只有当某个 Bean 已存在才创建
Demo
@Configuration
public class TransactionConfiguration {
@Bean
@ConditionalOnBean(DataSource.class)
public TransactionManager transactionManager(DataSource dataSource) {
return new TransactionManager(dataSource);
}
}
逻辑
存在 DataSource
↓
创建 TransactionManager
如果项目:没有配置数据库
则:TransactionManager 不会创建
@ConditionalOnProperty
作用 根据 配置文件属性 控制 Bean。
常用于:功能开关
application.yml
feature:
sms:
enabled: true
Demo
@Configuration
public class SmsConfiguration {
@Bean
@ConditionalOnProperty(
prefix = "feature.sms",
name = "enabled",
havingValue = "true"
)
public SmsService smsService() {
return new SmsService();
}
}
逻辑
feature.sms.enabled=true
↓
创建 SmsService
如果:false
则:Bean 不加载
@ConditionalOnExpression
作用 :通过 SpEL 表达式控制条件。
Demo
@Configuration
public class ExpressionConfig {
@Bean
@ConditionalOnExpression("${server.port} == 8080")
public PortService portService() {
return new PortService();
}
}
逻辑
server.port == 8080
↓
创建 PortService
@ConditionalOnJava
作用:根据 Java 版本判断。
Demo
@Configuration
@ConditionalOnJava(JavaVersion.SEVENTEEN)
public class Java17Configuration {
@Bean
public JavaFeatureService javaFeatureService() {
return new JavaFeatureService();
}
}
逻辑
JDK >= 17
↓
加载配置
常用于:JDK 特性适配
@ConditionalOnResource
作用 :判断 资源文件是否存在。
Demo
@Configuration
public class ResourceConfiguration {
@Bean
@ConditionalOnResource(resources = "classpath:config/app.properties")
public ResourceService resourceService() {
return new ResourceService();
}
}
逻辑
classpath 存在 config/app.properties
↓
创建 ResourceService
常用于:检测配置文件
@ConditionalOnWebApplication
作用 :只有在 Web 应用环境下生效。
Demo
@Configuration
@ConditionalOnWebApplication
public class WebMvcConfiguration {
@Bean
public WebService webService() {
return new WebService();
}
}
逻辑
SpringBoot Web项目
↓
配置生效
如果是:非 Web 项目
则:配置不会加载
@ConditionalOnNotWebApplication
与上面相反:
@Configuration
@ConditionalOnNotWebApplication
public class CliConfiguration {
@Bean
public CliService cliService() {
return new CliService();
}
}
适用于:
CLI工具
批处理程序
1.5 自定义条件注解
我们自己实现一个:
只有 Linux 才加载 Bean
-
定义 Condition
public class LinuxCondition implements Condition { @Override public boolean matches( ConditionContext context, AnnotatedTypeMetadata metadata) { String osName = context .getEnvironment() .getProperty("os.name"); return osName != null && osName.contains("Linux"); } }逻辑:读取系统变量,判断是否 Linux
-
使用 @Conditional
@Configuration public class AppConfig { @Bean @Conditional(LinuxCondition.class) public UserService userService() { return new UserService(); } }效果:
Linux -> Bean 创建
Windows -> Bean 不创建 -
测试
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); System.out.println(context.containsBean("userService"));输出:true / false;取决于操作系统。
常见使用方式
-
Bean 级别
@Bean @Conditional(LinuxCondition.class) public UserService userService(){ return new UserService(); }只控制当前 Bean。
-
Configuration 级别
@Configuration @Conditional(LinuxCondition.class) public class LinuxConfig { @Bean public UserService userService(){ return new UserService(); } }作用:整个配置类生效
1.6 Spring Boot 常见条件注解
@ConditionalOnClass
类存在才加载
常用于:
Starter自动配置
示例:
@Configuration
@ConditionalOnClass(name="redis.clients.jedis.Jedis")
public class RedisAutoConfiguration {
}
逻辑:
classpath存在Jedis -> 自动配置Redis
@ConditionalOnMissingBean
Bean不存在才创建
最常见。
示例:
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper(){
return new ObjectMapper();
}
意义:
用户自己定义Bean -> 不覆盖
用户没定义 -> 自动创建
@ConditionalOnProperty
根据配置文件。
application.yml
feature:
enable: true
代码:
@Bean
@ConditionalOnProperty(
name="feature.enable",
havingValue="true"
)
public FeatureService featureService(){
return new FeatureService();
}
1.7 业务场景用法
这是最常见的使用场景。
application.yml
feature:
sms: true
Condition
public class SmsCondition implements Condition {
@Override
public boolean matches(
ConditionContext context,
AnnotatedTypeMetadata metadata) {
return Boolean.parseBoolean(
context.getEnvironment()
.getProperty("feature.sms","false")
);
}
}
Bean
@Bean
@Conditional(SmsCondition.class)
public SmsService smsService(){
return new SmsService();
}
效果:
feature.sms=true -> 启用短信服务
feature.sms=false -> 不加载
二,在 Spring Boot 中的用途
Spring Boot 自动配置核心机制。
例如:
spring-boot-autoconfigure
内部逻辑:
1 判断classpath
2 判断Bean是否存在
3 判断配置
4 决定是否装配Bean
典型例子:
DataSourceAutoConfiguration
当:
存在 DataSource
存在 JDBC
存在配置
才会自动配置数据库。
三,条件注解执行流程
Spring 启动:
ConfigurationClassPostProcessor
↓
解析@Configuration
↓
解析@Bean
↓
检查@Conditional
↓
调用Condition.matches()
↓
true -> 注册Bean
false -> 忽略
四,封装自定义条件注解
可以封装自定义条件注解。
例如:
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(LinuxCondition.class)
public @interface ConditionalOnLinux {
}
使用:
@Bean
@ConditionalOnLinux
public UserService userService(){
return new UserService();
}
五,ConditionEvaluationReport 条件评估报告
在 Spring Boot 中,每一个条件判断都会被记录。
类:
ConditionEvaluationReport
作用:
记录:
哪个自动配置生效
哪个自动配置未生效
原因是什么
5.1 启用调试模式
配置:
debug=true
启动日志:
============================
CONDITIONS EVALUATION REPORT
============================
示例输出:
Positive matches:
-----------------
DataSourceAutoConfiguration matched
- @ConditionalOnClass found required class 'javax.sql.DataSource'
Negative matches:
-----------------
MongoAutoConfiguration
- @ConditionalOnClass did not find required class 'com.mongodb.MongoClient'
含义:
DataSource 自动配置 -> 生效
Mongo 自动配置 -> 不生效
5.2 条件报告生成流程
启动时:
ConditionEvaluator
↓
Condition.matches()
↓
记录结果
↓
ConditionEvaluationReport
关键类:
ConditionEvaluationReport
ConditionEvaluationReportLoggingListener
六,Spring Boot自动装配
6.1 Spring Boot 自动配置如何利用 Conditional 实现
在 Spring Boot 中,自动配置的核心目标是:
根据当前项目环境自动装配 Bean,而不是强制装配。
自动配置典型结构:
@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return createDataSource();
}
}
执行逻辑:
Spring Boot 启动
↓
加载 AutoConfiguration
↓
检查 Conditional 条件
↓
条件成立 -> 注册 Bean
条件不成立 -> 跳过
自动配置入口来自:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
旧版本(Boot 2.x):
META-INF/spring.factories
例如:
com.example.RedisAutoConfiguration
com.example.MysqlAutoConfiguration
启动时:
AutoConfigurationImportSelector
↓
加载 AutoConfiguration
↓
逐个解析 Conditional
核心类:
AutoConfigurationImportSelector
ConfigurationClassParser
ConditionEvaluator
6.2 手写Spring Boot Starter
目标:
my-redis-spring-boot-starter
功能:
自动配置 RedisClient
项目结构
my-redis-spring-boot-starter
│
├── my-redis-autoconfigure
│ RedisAutoConfiguration
│
├── my-redis-spring-boot-starter
│
└── pom.xml
AutoConfiguration
@Configuration
@ConditionalOnClass(RedisClient.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RedisClient redisClient(RedisProperties properties){
RedisClient client = new RedisClient();
client.setHost(properties.getHost());
client.setPort(properties.getPort());
return client;
}
}
配置类
@ConfigurationProperties(prefix="my.redis")
public class RedisProperties {
private String host = "localhost";
private int port = 6379;
// getter setter
}
注册自动配置
文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:
com.example.RedisAutoConfiguration
使用 Starter
应用项目:
<dependency>
<groupId>com.example</groupId>
<artifactId>my-redis-spring-boot-starter</artifactId>
</dependency>
配置:
my:
redis:
host: 127.0.0.1
port: 6379
Spring Boot 自动:
创建 RedisClient
七,Conditional 最重要的设计思想
Spring Boot 的自动配置哲学:
约定优于配置
自动配置遵循三条原则:
1 类存在
2 Bean不存在
3 配置开启
即:
@ConditionalOnClass
@ConditionalOnMissingBean
@ConditionalOnProperty
八,条件注解失效原因
最核心原因只有一个:
BeanDefinition 注册顺序问题
具体表现为:
| 原因 | 解释 |
|---|---|
| 配置类加载顺序 | BeanDefinition 未注册 |
| 父子容器 | Bean 不在同一个 Context |
| FactoryBean | 判断对象不同 |
| Bean 覆盖 | BeanDefinition 被替换 |
| proxyBeanMethods | Bean 创建方式变化 |
8.1 配置类加载顺序
问题场景
@Configuration
public class AConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
}
@Configuration
public class BConfig {
@Bean
@ConditionalOnBean(ServiceA.class)
public ServiceB serviceB() {
return new ServiceB();
}
}
理论上:
ServiceA 存在
→ ServiceB 应该创建
但在实际运行中,有时:
ServiceB 没有创建
即 @ConditionalOnBean 条件判断失败。
根本原因:BeanDefinition 解析顺序
Spring 在启动时并不是:
先创建 Bean
再判断 Conditional
而是:
先解析 BeanDefinition
再注册 BeanDefinition
最后实例化 Bean
关键流程:
ConfigurationClassParser
↓
解析@Configuration
↓
注册 BeanDefinition
↓
执行 Condition
而 @ConditionalOnBean 判断的是:
BeanDefinition 是否存在
而不是:
Bean 实例是否存在
因此问题往往是:
判断时 BeanDefinition 还没有注册
解决方案
-
@AutoConfigureAfter
如果是自动配置:
@Configuration @AutoConfigureAfter(AutoConfigA.class) public class AutoConfigB { }作用:确保 AutoConfigA 先加载
-
使用 @ConditionalOnBean(name=...)
Spring 的 Bean 判断有三种方式:
类型 名称 注解示例:
@ConditionalOnBean(name="serviceA")有时名称判断更稳定。
-
使用 @Import
@Configuration @Import(AConfig.class) public class BConfig { }这样:AConfig 一定先加载
8.2 父子容器
Spring 有多个容器:
Root Context
Web Context
例如:
Spring MVC
结构:
Root ApplicationContext
│
└── DispatcherServlet Context
如果:
ServiceA 在 RootContext
ServiceB 在 WebContext
可能导致:
@ConditionalOnBean 判断失败
8.3 FactoryBean
如果 Bean 是:
FactoryBean
Spring 实际注册的是:
factoryBeanName
而不是:
productBean
例如:
&redisConnectionFactory
所以:
@ConditionalOnBean(RedisConnectionFactory.class)
可能失效。
8.4 Bean 覆盖
如果:
用户定义 Bean
并且:
spring.main.allow-bean-definition-overriding=true
则可能:
原 BeanDefinition 被覆盖
导致 Conditional 判断异常。
8.5 proxyBeanMethods
Spring @Configuration 默认:
proxyBeanMethods = true
如果关闭:
@Configuration(proxyBeanMethods = false)
可能导致:
Bean 注册行为不同
某些 Conditional 判断可能受影响。
8.6 调试 Conditional 失效
最有效方法:
开启 debug。
debug=true
Spring 会打印:
CONDITIONS EVALUATION REPORT
示例:
ServiceBAutoConfiguration
Did not match:
- @ConditionalOnBean (types: ServiceA) did not find any beans
说明:
Spring 在判断时没有找到 ServiceA
核心类:
ConditionEvaluationReport
8.7 Spring Boot 自动配置的正确写法
Spring 官方推荐:
@Configuration
@ConditionalOnClass(ServiceA.class)
public class ServiceBAutoConfiguration {
@Bean
@ConditionalOnBean(ServiceA.class)
@ConditionalOnMissingBean
public ServiceB serviceB(){
return new ServiceB();
}
}
三层保护:
Class 存在
Bean 存在
Bean 未定义