1. 概述
本文将深入解析 Spring 中一个非常实用但容易被忽视的注解:@ConditionalOnProperty
。
你可能在写自动配置或需要根据配置动态加载 Bean 时遇到过这种需求:只有当某个配置项存在且值满足条件时,才注册某个 Bean。这时候 @ConditionalOnProperty
就派上用场了。
我们将从基本用法讲起,再到实际场景中的高级配置,帮你彻底掌握这个“条件开关”,避免踩坑。
2. @ConditionalOnProperty 的作用
在 Spring(尤其是 Spring Boot)开发中,我们经常需要根据配置文件中的属性来决定是否创建某个 Bean。比如:
- 开发环境用 Mock 服务,生产环境用真实服务
- 是否启用某个功能模块(如短信通知、邮件服务)
- 切换数据源(测试库 vs 生产库)
✅ @ConditionalOnProperty
正是为此设计的——它是一个条件注解,用于控制 Bean 的注册时机。
它的核心逻辑是:只有当指定的配置属性存在,并且其值符合条件时,被标注的 Bean 才会被注入到 Spring 容器中。
默认情况下:
- 属性必须存在
- 值不能为
false
(即true
、yes
、on
、1
等会被视为 true)
⚠️ 注意:它属于 Spring Boot 的条件化配置机制的一部分,位于 spring-boot-autoconfigure
模块中,所以确保你的项目引入了相关依赖。
3. 实际使用示例
我们通过一个简单的通知系统来演示它的用法。
3.1 定义接口与实现
先定义一个通知发送接口:
public interface NotificationSender {
String send(String message);
}
然后实现邮件通知:
public class EmailNotification implements NotificationSender {
@Override
public String send(String message) {
return "Email Notification: " + message;
}
}
3.2 使用 @ConditionalOnProperty 控制 Bean 加载
我们现在希望:只有当配置了 notification.service
属性时,才加载 EmailNotification
这个 Bean。
配置如下:
@Configuration
public class NotificationConfig {
@Bean(name = "emailNotification")
@ConditionalOnProperty(prefix = "notification", name = "service")
public NotificationSender notificationSender() {
return new EmailNotification();
}
}
📌 关键参数说明:
prefix = "notification"
:配置前缀name = "service"
:属性名- 合起来就是检查
notification.service
是否存在
3.3 配置 application.properties
在 application.properties
中添加:
notification.service=email
此时,EmailNotification
Bean 就会被成功加载。
❌ 如果你不配置这个属性,或者设置为 notification.service=
(空值),该 Bean 不会注册。
4. 高级用法与技巧
4.1 根据属性值精确匹配 —— havingValue
上面的例子只判断属性是否存在。但很多时候我们需要根据具体值来决定加载哪个实现。
比如我们现在加一个短信通知:
public class SmsNotification implements NotificationSender {
@Override
public String send(String message) {
return "SMS Notification: " + message;
}
}
我们希望:
notification.service=email
→ 加载 EmailNotificationnotification.service=sms
→ 加载 SmsNotification
这时就需要用到 havingValue
属性:
@Bean(name = "smsNotification")
@ConditionalOnProperty(
prefix = "notification",
name = "service",
havingValue = "sms"
)
public NotificationSender notificationSender2() {
return new SmsNotification();
}
✅ 效果:只有当 notification.service=sms
时,SmsNotification
才会被加载。
而之前的 EmailNotification
只检查属性是否存在,因此只要不是 false
,都会加载。这可能会导致两个 Bean 同时存在。
4.2 如何避免多个 Bean 冲突?
简单粗暴的方式是:也让 Email 的加载也加上 havingValue
判断:
@Bean(name = "emailNotification")
@ConditionalOnProperty(
prefix = "notification",
name = "service",
havingValue = "email"
)
public NotificationSender emailNotification() {
return new EmailNotification();
}
这样就能实现互斥加载,保证只有一个通知服务被启用。
4.3 处理属性缺失的情况 —— matchIfMissing
有时候我们希望:如果用户没配这个属性,默认启用某个功能。
这就用到 matchIfMissing
参数:
@Bean(name = "emailNotification")
@ConditionalOnProperty(
prefix = "notification",
name = "service",
havingValue = "email",
matchIfMissing = true
)
public NotificationSender emailNotification() {
return new EmailNotification();
}
✅ matchIfMissing = true
表示:即使 notification.service
没有配置,也认为条件成立。
⚠️ 注意:matchIfMissing
和 havingValue
一起使用时要小心。如果属性不存在,havingValue
的比较不会生效,而是直接通过(因为条件“缺失即匹配”)。
4.4 多属性支持 —— name 数组
你还可以同时检查多个属性:
@ConditionalOnProperty(
prefix = "notification",
name = {"service", "enabled"},
havingValue = "true"
)
表示:notification.service=true
且 notification.enabled=true
才加载。
4.5 测试验证
下面是一个典型的测试用例,验证配置生效:
@Test
public void whenValueSetToEmail_thenCreateEmailNotification() {
this.contextRunner
.withPropertyValues("notification.service=email")
.withUserConfiguration(NotificationConfig.class)
.run(context -> {
assertThat(context).hasBean("emailNotification");
NotificationSender sender = context.getBean(EmailNotification.class);
assertThat(sender.send("Hello From Baeldung!"))
.isEqualTo("Email Notification: Hello From Baeldung!");
assertThat(context).doesNotHaveBean("smsNotification");
});
}
✅ 该测试模拟了 Spring 上下文启动,设置属性并验证 Bean 是否正确加载。
5. 总结
@ConditionalOnProperty
是 Spring Boot 条件化配置中非常实用的工具,适合用于:
- 功能开关控制
- 多实现类的条件加载
- 自动配置模块的精细化控制
📌 核心要点回顾:
特性 | 说明 |
---|---|
prefix + name |
指定要检查的配置项,如 app.feature |
havingValue |
要求属性值必须等于指定字符串 |
matchIfMissing |
属性不存在时是否默认匹配(可用于设默认开启) |
多 name 支持 |
可同时检查多个属性 |
✅ 推荐使用场景:
- 自定义 Starter 中的自动配置
- 多环境差异化 Bean 注册
- 第三方服务可选集成(如是否启用 Redis 缓存)
❌ 避免滥用:
- 不要用它替代
@Profile
(环境隔离还是用 Profile 更清晰) - 不要嵌套太多条件导致逻辑复杂难维护
源码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/spring-boot-modules/spring-boot-autoconfiguration