@ConditionalOnProperty 注解
一句话定位:
@ConditionalOnProperty是 Spring Boot 自动配置的配置属性开关。它让 Bean 的注册与否完全受控于application.yml或application.properties中的某个键值对,实现"一键启停"功能模块的能力。
定义与作用
@ConditionalOnProperty 根据 Spring Environment 中的配置属性值 决定是否注册 Bean 或加载配置类。它是将"业务开关"从代码硬编码中解耦出来的标准手段。
与 @ConditionalOnClass 不同,@ConditionalOnClass 判断的是"有没有依赖"(静态条件),而 @ConditionalOnProperty 判断的是"用户是否显式开启"(动态条件)。二者经常组合使用:先检查类路径有依赖,再检查用户是否启用了该功能。
典型应用场景
| 场景 | 配置示例 | 效果 |
|---|---|---|
| 功能开关 | feixiang.student.cache.enabled=true | 开启 Redis 缓存 |
| 多实现切换 | feixiang.student.storage.type=oss | 启用 OSS 存储,禁用本地存储 |
| 环境特性 | spring.profiles.active=dev | 配合 @Profile 使用 |
| 安全特性 | feixiang.student.encryption.enabled=true | 开启请求加密拦截器 |
适用位置与常用属性
适用位置
@ConditionalOnProperty 可以标注在:
- 类级别:控制整个配置类是否生效
- @Bean 方法级别:控制单个 Bean 是否注册
// 类级别:整个功能模块受控
@Configuration
@ConditionalOnProperty(prefix = "feixiang.student.cache", name = "enabled", havingValue = "true")
public class CacheConfiguration { ... }
// 方法级别:单个 Bean 受控
@Configuration
public class StorageConfiguration {
@Bean
@ConditionalOnProperty(prefix = "feixiang.student.storage", name = "type", havingValue = "local")
public StorageService localStorage() { ... }
@Bean
@ConditionalOnProperty(prefix = "feixiang.student.storage", name = "type", havingValue = "oss")
public StorageService ossStorage() { ... }
}
常用属性
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
value / name | String[] | 要检查的配置属性名 | 无 |
prefix | String | 配置属性的前缀,与 name 拼接为完整键 | "" |
havingValue | String | 期望的属性值,匹配时条件成立 | "" |
matchIfMissing | boolean | 当配置属性不存在时,是否视为匹配 | false |
属性组合规则:
- 最终检查的配置键 =
prefix+.+name - 如果
havingValue未指定(默认""),则只要属性存在且不为false即匹配 - 如果
matchIfMissing = true,则属性不存在时也视为匹配(常用于默认开启的功能)
核心原理
属性值匹配流程
与 @ConditionalOnClass 的组合决策
设计哲学:@ConditionalOnClass 解决"能不能配"的问题,@ConditionalOnProperty 解决"用户想不想配"的问题。二者叠加,既避免了缺少依赖时的启动失败,又赋予了用户显式控制权。
完整示例
场景简述
飞翔科技公司的学生成绩管理系统需要支持多种文件存储方式:本地磁盘存储和阿里云 OSS 存储。架构师白歌要求:
- 通过
application.yml中的一个配置项切换存储实现 - 默认使用本地存储(不配置时也生效)
- 切换到 OSS 时,必须同时检查 OSS 相关依赖是否在类路径中
小崔需要设计一个基于 @ConditionalOnProperty 的存储策略自动切换方案。
操作前:硬编码实现导致切换困难
// 操作前:错误示范,实现类选择硬编码
package com.feixiang.student.service;
import org.springframework.stereotype.Service;
@Service
public class FileService {
private final StorageService storage;
public FileService() {
// 硬编码!每次切换都要改代码、重新编译
this.storage = new LocalStorageService();
// this.storage = new OssStorageService(); // 想换 OSS?改代码!
}
public String upload(byte[] fileData, String filename) {
return storage.store(fileData, filename);
}
}
痛点:
- 存储实现切换需要修改源码并重新打包
- 无法在不同环境(开发用本地、生产用 OSS)使用不同实现
- 如果 OSS 依赖未引入,直接
new OssStorageService()会编译失败
使用该注解的完整代码
小崔首先定义存储接口和两种实现:
package com.feixiang.student.service;
public interface StorageService {
String store(byte[] fileData, String filename);
}
package com.feixiang.student.service;
import org.springframework.stereotype.Component;
@Component
public class LocalStorageService implements StorageService {
@Override
public String store(byte[] fileData, String filename) {
// 模拟保存到本地磁盘
return "/data/feixiang/upload/" + filename;
}
}
然后创建条件化的配置类:
package com.feixiang.student.config;
import com.feixiang.student.service.LocalStorageService;
import com.feixiang.student.service.OssStorageService;
import com.feixiang.student.service.StorageService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* 存储策略自动配置
* 根据 feixiang.student.storage.type 配置切换实现
*/
@Configuration(proxyBeanMethods = false)
public class StorageAutoConfiguration {
/**
* 本地存储:默认实现
* matchIfMissing = true 表示当配置不存在时,默认启用本地存储
*/
@Bean
@Primary
@ConditionalOnProperty(
prefix = "feixiang.student.storage",
name = "type",
havingValue = "local",
matchIfMissing = true // 默认开启
)
public StorageService localStorageService() {
return new LocalStorageService();
}
/**
* OSS 存储:需要显式配置 type=oss,且类路径有 OSS SDK
*/
@Bean
@ConditionalOnClass(name = "com.aliyun.oss.OSSClient") // 检查 OSS SDK 是否存在
@ConditionalOnProperty(
prefix = "feixiang.student.storage",
name = "type",
havingValue = "oss"
)
public StorageService ossStorageService() {
return new OssStorageService();
}
}
OSS 实现类(需引入 aliyun-sdk-oss 依赖):
package com.feixiang.student.service;
import com.aliyun.oss.OSSClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class OssStorageService implements StorageService {
@Value("${feixiang.student.storage.oss.endpoint}")
private String endpoint;
@Value("${feixiang.student.storage.oss.bucket}")
private String bucket;
@Override
public String store(byte[] fileData, String filename) {
// 模拟 OSS 上传
return "https://" + bucket + "." + endpoint + "/" + filename;
}
}
对应的 application.yml 配置:
# 场景 A:开发环境,使用本地存储(不写 storage.type 或写 local)
feixiang:
student:
storage:
type: local # 不写这一行也行,因为 matchIfMissing = true
---
# 场景 B:生产环境,使用 OSS(需放在 application-prod.yml 中)
feixiang:
student:
storage:
type: oss
oss:
endpoint: oss-cn-guangzhou.aliyuncs.com
bucket: feixiang-student-prod
业务代码中无感知注入:
package com.feixiang.student.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class FileService {
private final StorageService storageService;
@Autowired
public FileService(StorageService storageService) {
this.storageService = storageService;
}
public String uploadStudentAvatar(byte[] imageData, Long studentId) {
String filename = "avatar_" + studentId + ".jpg";
String url = storageService.store(imageData, filename);
System.out.println("[飞翔科技] 学生头像已保存至: " + url);
return url;
}
}
操作后运行结果及分析
场景 A:开发环境(type = local 或未配置)
2024-05-20 10:30:15.123 INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration :
localStorageService matched:
@ConditionalOnProperty (feixiang.student.storage.type=local, matchIfMissing=true)
2024-05-20 10:30:15.456 INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration :
ossStorageService did not match:
@ConditionalOnProperty (feixiang.student.storage.type) did not find value 'oss'
[飞翔科技] 学生头像已保存至: /data/feixiang/upload/avatar_2024001.jpg
场景 B:生产环境(type = oss,且类路径有 aliyun-sdk-oss)
2024-05-20 10:35:22.789 INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration :
ossStorageService matched:
@ConditionalOnClass found 'com.aliyun.oss.OSSClient'
@ConditionalOnProperty (feixiang.student.storage.type=oss)
2024-05-20 10:35:22.901 INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration :
localStorageService did not match:
@ConditionalOnProperty (feixiang.student.storage.type) found value 'oss', expected 'local'
[飞翔科技] 学生头像已保存至: https://feixiang-student-prod.oss-cn-guangzhou.aliyuncs.com/avatar_2024001.jpg
分析:
- 默认行为:开发环境未配置
storage.type时,matchIfMissing = true使本地存储默认生效。 - 显式切换:生产环境配置
type: oss后,OSS 存储 Bean 被注册,本地存储 Bean 因havingValue不匹配而被跳过。 - 双重保险:OSS 配置同时受
@ConditionalOnClass保护,即使配置写错为type: oss,如果类路径没有阿里云 SDK,也不会尝试注册OssStorageService,避免ClassNotFoundException。
易错场景与面试考点
易错场景一:havingValue 与配置值类型不匹配
小崔在配置文件中写:
feixiang:
student:
cache:
enabled: true # YAML 布尔值
但条件注解中误用字符串比较:
// 错误示范:havingValue 大小写不匹配
@Bean
@ConditionalOnProperty(
prefix = "feixiang.student.cache",
name = "enabled",
havingValue = "TRUE" // ← 大写,与 YAML 的 true 不匹配
)
public CacheManager cacheManager() { ... }
后果:@ConditionalOnProperty 使用字符串比较,"TRUE" 不等于 "true",条件不匹配,cacheManager Bean 未被注册。小崔排查半天,发现是大小写问题。
正确做法:havingValue 必须与配置值的字符串形式完全一致。YAML 的布尔值 true 在 Environment 中转为字符串 "true",因此 havingValue = "true" 才能匹配。
易错场景二:matchIfMissing 的误用导致功能意外开启
// 错误示范:敏感功能默认开启
@Bean
@ConditionalOnProperty(
prefix = "feixiang.student.security",
name = "debug-mode",
matchIfMissing = true // ← 危险!用户没配置时默认开启调试模式
)
public DebugInterceptor debugInterceptor() {
return new DebugInterceptor(); // 打印所有请求参数,包括密码!
}
后果:生产环境忘记配置 debug-mode=false 时,调试拦截器默认生效,可能导致敏感信息泄露。
正确做法:涉及安全、性能、成本的功能,应将 matchIfMissing 设为 false(默认),要求用户显式开启。只有无害的便利功能(如本地存储)才适合默认开启。
面试考点
Q:@ConditionalOnProperty 和 @Profile 有什么区别?
@Profile基于 Spring 的Environment.getActiveProfiles()判断,用于区分开发、测试、生产等环境维度;@ConditionalOnProperty基于任意配置属性判断,用于控制功能维度的开关。二者可以组合使用,例如@Profile("prod")+@ConditionalOnProperty("feature.x.enabled")。
Q:havingValue 未指定时,什么情况下条件会匹配?
当
havingValue为默认值""时,只要配置属性存在且值不为false(字符串比较),条件即匹配。这意味着enabled: true、enabled: yes、enabled: 1都能匹配,但enabled: false不会匹配。
Q:能否在一个类上同时使用多个 @ConditionalOnProperty?
不能直接重复使用同一注解,但可以通过组合条件实现。Spring Boot 提供了
@ConditionalOnExpression(基于 SpEL)和自定义@Conditional实现来处理多条件逻辑。或者将多个条件拆分到不同的@Bean方法上。
Q:@ConditionalOnProperty 支持检查 List 或 Map 类型的配置吗?
name属性支持数组形式,例如@ConditionalOnProperty(name = {"a", "b"})表示a和b都必须存在。但它不直接支持检查 YAML 中的 List 结构(如feixiang.list[0])。对于复杂结构,应配合@ConfigurationProperties将配置绑定到 POJO,再在业务逻辑中判断。