一句话定位:
@ConditionalOnProperty是 Spring Boot 自动配置的配置属性开关,它根据application.yml(或application.properties)中的指定属性值来决定是否启用某配置类或 Bean,实现"配置驱动行为"——无需改代码,只改配置即可控制功能启停。
定义与作用
@ConditionalOnProperty 是 Spring Boot 条件注解家族中最灵活的一员。它不像 @ConditionalOnClass 那样只能检查类路径,也不像 @ConditionalOnMissingBean 那样只能检查容器状态——它检查的是外部配置属性,让开发者通过修改 YAML 文件就能控制应用行为。
它的典型应用场景包括:
| 场景 | 说明 | 示例 |
|---|---|---|
| 功能开关 | 通过配置控制某个功能是否启用 | 是否启用短信发送、是否开启性能监控 |
| 环境差异化 | 不同环境使用不同的配置策略 | 开发环境启用 Mock,生产环境启用真实服务 |
| 模块热插拔 | 在不修改代码的情况下禁用某个模块 | 禁用缓存模块、禁用定时任务 |
对比传统 Spring:在传统 SSM 中,要实现"功能开关"通常需要定义布尔值 Bean 并在业务代码中判断,或者使用 @Profile 但只能按环境区分。@ConditionalOnProperty 提供了更细粒度的、基于任意属性值的控制。
适用位置与常用属性
适用位置
@ConditionalOnProperty 可以标注在:
- 类级别:控制整个配置类是否生效
- @Bean 方法级别:控制单个 Bean 是否注册
// 类级别:整个功能模块受控
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "feixiang.sms", name = "enabled", havingValue = "true")
public class SmsAutoConfiguration { ... }
// 方法级别:单个 Bean 受控
@Configuration
public class FeatureConfig {
@Bean
@ConditionalOnProperty(prefix = "feixiang.audit", name = "enabled", havingValue = "true")
public AuditLogger auditLogger() {
return new AuditLogger();
}
}
常用属性
| 属性 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
prefix | String | 否 | 属性前缀,会自动追加 . | prefix = "feixiang.cache" |
name | String[] | 是 | 属性名(或数组),与 prefix 拼接成完整属性名 | name = "enabled" → feixiang.cache.enabled |
havingValue | String | 否 | 属性必须等于的值,条件才匹配 | havingValue = "true" |
matchIfMissing | boolean | 否 | 当属性不存在时,是否默认匹配 | matchIfMissing = false(默认) |
属性拼接规则:
prefix+.+name= 完整属性名。如果prefix为空,则直接使用name。
核心原理
配置属性读取与匹配机制
配置属性来源与优先级
关键理解:@ConditionalOnProperty 读取的是 Environment 中的最终属性值,这意味着它自动继承 Spring Boot 的 17 级配置优先级——命令行参数可以覆盖 YAML 文件,环境变量可以覆盖系统属性。这为不同环境(dev/test/prod)控制功能开关提供了极大灵活性。
完整示例
场景简述
飞翔科技(广州)的学生成绩管理系统 com.feixiang.student 需要实现一个成绩导出功能。架构师白歌要求:该功能默认关闭(避免生产环境意外导出大量数据),但可以通过 application.yml 开启;同时支持 csv 和 excel 两种导出格式,通过配置属性切换。
小崔需要设计一个配置类,让导出功能的启用和格式都通过外部配置控制。
操作前:代码中硬编码开关
// 操作前:错误示范,功能开关硬编码在代码中
package com.feixiang.student.service;
import org.springframework.stereotype.Service;
@Service
public class ExportService {
private static final boolean EXPORT_ENABLED = true; // ← 硬编码!
private static final String EXPORT_FORMAT = "csv"; // ← 硬编码!
public String exportStudentScores() {
if (!EXPORT_ENABLED) {
return "导出功能已禁用";
}
if ("csv".equals(EXPORT_FORMAT)) {
return "导出 CSV 格式的学生成绩(学号2024001等)";
} else {
return "导出 Excel 格式的学生成绩";
}
}
}
后果:每次需要切换导出格式或关闭导出功能时,小崔必须修改代码、重新编译、重新打包、重新部署。飞翔科技的生产环境禁止导出功能,但小崔忘记改代码,导致上线后数据泄露风险。白歌在代码审查时发现了这个问题。
使用该注解的完整代码
小崔改用 @ConditionalOnProperty 实现配置驱动:
步骤 1:创建配置属性类
package com.feixiang.student.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "feixiang.export")
public class ExportProperties {
private boolean enabled = false; // 默认关闭
private String format = "csv"; // 默认 CSV
// getter / setter
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getFormat() { return format; }
public void setFormat(String format) { this.format = format; }
}
步骤 2:创建条件化的导出配置类
package com.feixiang.student.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 成绩导出功能自动配置类
* 只有当 feixiang.export.enabled=true 时才生效
*
* @author 小崔
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(
prefix = "feixiang.export",
name = "enabled",
havingValue = "true",
matchIfMissing = false // 默认不启用,必须显式开启
)
public class ExportAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = "feixiang.export", name = "format", havingValue = "csv")
public Exporter csvExporter(ExportProperties properties) {
return new CsvExporter(properties);
}
@Bean
@ConditionalOnProperty(prefix = "feixiang.export", name = "format", havingValue = "excel")
public Exporter excelExporter(ExportProperties properties) {
return new ExcelExporter(properties);
}
}
步骤 3:定义导出器接口与实现
package com.feixiang.student.config;
public interface Exporter {
String export(Long studentId);
}
public class CsvExporter implements Exporter {
private final ExportProperties properties;
public CsvExporter(ExportProperties properties) {
this.properties = properties;
}
@Override
public String export(Long studentId) {
return "CSV 导出:学生 " + studentId + " 的成绩记录";
}
}
public class ExcelExporter implements Exporter {
private final ExportProperties properties;
public ExcelExporter(ExportProperties properties) {
this.properties = properties;
}
@Override
public String export(Long studentId) {
return "Excel 导出:学生 " + studentId + " 的成绩记录";
}
}
步骤 4:在 application.yml 中配置
# application.yml
feixiang:
export:
enabled: true
format: csv
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_db
username: root
password: secret
步骤 5:业务代码中注入
package com.feixiang.student.service;
import com.feixiang.student.config.Exporter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
private final Exporter exporter;
@Autowired
public StudentService(@Autowired(required = false) Exporter exporter) {
this.exporter = exporter;
}
public String exportScore(Long studentId) {
if (exporter == null) {
return "导出功能未启用";
}
return exporter.export(studentId);
}
}
操作后运行结果及分析
场景 A:启用 CSV 导出
feixiang:
export:
enabled: true
format: csv
启动日志:
2024-05-20 15:00:12.345 INFO 12345 --- [main] c.f.s.c.ExportAutoConfiguration :
ExportAutoConfiguration matched:
@ConditionalOnProperty matched feixiang.export.enabled=true
2024-05-20 15:00:12.567 INFO 12345 --- [main] c.f.s.c.ExportAutoConfiguration :
CsvExporter bean registered
场景 B:启用 Excel 导出
feixiang:
export:
enabled: true
format: excel
启动日志:
2024-05-20 15:05:22.789 INFO 12345 --- [main] c.f.s.c.ExportAutoConfiguration :
ExportAutoConfiguration matched: feixiang.export.enabled=true
2024-05-20 15:05:22.901 INFO 12345 --- [main] c.f.s.c.ExportAutoConfiguration :
ExcelExporter bean registered
场景 C:禁用导出(默认)
feixiang:
export:
enabled: false
或不配置该属性(matchIfMissing = false)
启动日志:
2024-05-20 15:10:33.123 INFO 12345 --- [main] o.s.b.a.c.AutoConfigurationReport :
ExportAutoConfiguration:
Did not match:
- @ConditionalOnProperty (feixiang.export.enabled=true) did not find property 'enabled'
2024-05-20 15:10:33.456 INFO 12345 --- [main] c.f.s.StudentManagementApplication :
Started StudentManagementApplication in 1.234 seconds
分析:
- 功能开关:
@ConditionalOnProperty将feixiang.export.enabled作为总开关。matchIfMissing = false确保默认情况下功能关闭,必须显式配置enabled: true才能开启。 - 格式切换:
CsvExporter和ExcelExporter分别由各自的@ConditionalOnProperty控制,根据feixiang.export.format的值决定注册哪一个。由于两者是互斥的havingValue,不会同时注册。 - 安全设计:生产环境只要不配置
feixiang.export.enabled: true,导出功能模块就完全不会加载,避免了数据导出风险。
易错场景与面试考点
易错场景一:havingValue 与属性值类型不匹配
# application.yml
feixiang:
cache:
enabled: true # 布尔值
// 错误示范:havingValue 写成字符串 "1"
@Configuration
@ConditionalOnProperty(
prefix = "feixiang.cache",
name = "enabled",
havingValue = "1" // ← 错误!YAML 中是 true,不是 "1"
)
public class CacheAutoConfiguration { ... }
后果:@ConditionalOnProperty 是按字符串比较的。Environment.getProperty() 读取到的值是字符串 "true",而 havingValue 是 "1",两者不相等,条件不匹配,CacheAutoConfiguration 被跳过。小崔排查半天发现 havingValue 写错了。
正确做法:havingValue 必须与配置文件中的值严格一致。YAML 的布尔值 true 对应字符串 "true":
@ConditionalOnProperty(
prefix = "feixiang.cache",
name = "enabled",
havingValue = "true" // 正确:与 YAML 中的 true 对应
)
易错场景二:matchIfMissing = true 导致意外的默认开启
// 错误示范:默认开启,但开发者误以为默认关闭
@Configuration
@ConditionalOnProperty(
prefix = "feixiang.monitoring",
name = "enabled",
havingValue = "true",
matchIfMissing = true // ← 危险!属性不存在时默认开启
)
public class MonitoringAutoConfiguration { ... }
后果:小崔在 application.yml 中没有配置 feixiang.monitoring.enabled,但由于 matchIfMissing = true,MonitoringAutoConfiguration 仍然被加载。生产环境中这个监控模块消耗了大量资源,但小崔以为它是关闭的。
正确做法:对于可能消耗资源或涉及安全的功能,应使用 matchIfMissing = false(默认),要求显式开启:
@ConditionalOnProperty(
prefix = "feixiang.monitoring",
name = "enabled",
havingValue = "true",
matchIfMissing = false // 安全:默认关闭
)
面试考点
Q:@ConditionalOnProperty 的 havingValue 和 matchIfMissing 如何配合使用?
havingValue指定属性必须等于的值(字符串精确匹配);matchIfMissing指定当属性完全不存在时条件的默认行为。常见组合:
- 默认关闭(推荐):
havingValue = "true",matchIfMissing = false→ 必须显式写enabled: true才开启- 默认开启:
havingValue = "true",matchIfMissing = true→ 不配置时默认开启,配置enabled: false时关闭- 严格匹配(无默认值):
havingValue = "prod"→ 只有spring.profiles.active=prod时生效
Q:@ConditionalOnProperty 可以检查多个属性吗?
可以。
name属性是String[]数组,可以同时指定多个属性名。此时要求所有属性都满足条件(AND 关系)。例如@ConditionalOnProperty(prefix = "app", name = {"feature-a", "feature-b"}, havingValue = "true")要求app.feature-a=true且app.feature-b=true同时成立。
Q:@ConditionalOnProperty 与 Spring 的 @Profile 有什么区别?
@Profile基于激活的 Profile(如 dev/test/prod)做条件判断,粒度较粗,只能按环境区分;@ConditionalOnProperty基于任意配置属性做判断,粒度更细,可以按功能开关、按配置值、按业务标识等任意维度控制。两者可以结合使用:@Profile("prod")+@ConditionalOnProperty(...)表示"只在生产环境且属性满足时才生效"。
Q:@ConditionalOnProperty 读取的是最终合并后的属性值吗?
是的。它通过
Environment.getProperty()读取,自动继承 Spring Boot 的 17 级配置优先级。这意味着命令行参数--feixiang.export.enabled=true可以覆盖application.yml中的feixiang.export.enabled: false,让运维人员在启动时临时开启功能,无需修改配置文件。