前言
在SpringBoot
出现之前,应用配置往往通过@Value
注解或XML
文件完成。但随着微服务架构的普及,配置复杂度也是急剧上升:
- 不同环境要不同配置
- 多环境部署、多数据源
- 功能要能开关(比如短信功能上线前先关掉)
- 第三方服务(微信、支付宝)要对接
- 数据源、缓存、日志......全都得配
这时候,传统方式就扛不住了。
于是,SpringBoot出手了!
它提出了两大核心理念:
- 外部化配置 :把配置从代码里拿出来,写在
application.yml
这种文件里。 - 自动配置:你加个依赖,它自动帮你配好 Bean,不用手动 new。
而支撑这两个"神操作"的两大核心技术,就是我们今天要聊的: 而支撑这两大理念的核心技术,就是我们这篇文章要深入探讨的:
@ConfigurationProperties
:把配置文件变成 Java 对象
@Conditional
:按条件决定要不要创建 Bean
学懂它们,你的Spring应用就从"手动挡"升级成"自动驾驶"了!
一、@ConfigurationProperties
1. 什么是 @ConfigurationProperties
?
类型安全的配置绑定,@ConfigurationProperties
是 SpringBoot 提供的一个注解,用于将外部配置(如 application.yml
)自动绑定到Java对象中,实现类型安全、结构清晰、易于维护的配置管理。
它支持嵌套对象、集合、类型转换、松散绑定,并可结合校验框架进行配置验证。
2. 为什么优于 @Value
?
假设我们要读取一组应用配置:
yaml
app:
name: "我的应用"
version: "1.0.0"
description: "这是一个示例应用"
servers:
- "192.168.1.1"
- "192.168.1.2"
若使用 @Value
,代码会变得冗长且难以维护:
java
@Component
public class MyConfig {
@Value("${app.name}")
private String name;
@Value("${app.version}")
private String version;
@Value("${app.description}")
private String description;
@Value("${app.servers}")
private List<String> servers; // 还需手动解析!
}
而使用 @ConfigurationProperties
,一切变得简洁:
java
@Configuration
@ConfigurationProperties(prefix = "app")
@Data // Lombok 自动生成 getter/setter/toString
public class AppConfig {
private String name;
private String version;
private String description;
private List<String> servers;
}
只需一行注解,即可完成自动绑定、类型转换、集合解析。
2. 松散绑定:名字怎么写都行!
比如:
- Java 里叫
serverPort
- 配置文件里写成
server-port
或server_port
用@Value
时,必须完全匹配,否则报错。
但@ConfigurationProperties
很聪明,它支持"松散绑定",意思是:
Java 属性名 | 你可以在 YAML 里写成 |
---|---|
serverPort |
server-port , server_port , SERVER_PORT , serverPort |
再也不用担心命名风格不统一了。
3. 配置验证:防止非法配置
配置错误往往在运行时才暴露,导致系统崩溃。我们可以通过 @Validated
+ JSR-303 校验提前发现问题。
java
@Configuration
@ConfigurationProperties(prefix = "email")
@Validated
@Data
public class EmailAutoConfig {
@NotBlank(message = "邮件主机不能为空")
private String host;
@Min(value = 1, message = "端口必须大于0")
@Max(value = 65535, message = "端口不能超过65535")
private int port = 25;
@Email(message = "邮箱格式不正确")
private String username;
}
如果配置是这样的:
yaml
email:
host: ""
port: 99999
username: "not-an-email"
程序启动直接失败,并提示:
port
超过 65535
host
不能为空
username
邮箱格式不对
好处:问题早发现,不等到上线才出事!
4. IDE智能提示:写配置像写代码一样爽
你有没有在写 application.yml
时,想不起某个配置项叫什么?
加个依赖,就能让 IDE 给你提示!
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
加完后,你写 app.
,IDE 就会自动提示 name
、version
、servers
等字段,还能看到注释说明。
开发体验直接拉满!
二、@Conditional
1. 它是干嘛的?
条件化装配的核心机制
想象一下,你有个功能叫"短信服务",但你不想每次都加载它。
- 开发环境:不需要发短信
- 测试环境:可以模拟
- 生产环境:才真正启用
怎么实现?用 @Conditional
!
它就像一个安检门,只有满足条件的 Bean 才能进入Spring容器。
2. 常见的安检规则
条件注解 | 意思 |
---|---|
@ConditionalOnProperty |
配置里有某个值才加载 |
@ConditionalOnClass |
项目里有某个类才加载(比如 Redis) |
@ConditionalOnMissingBean |
容器里还没有这个 Bean 才创建 |
@ConditionalOnWebApplication |
只有是 Web 项目才加载 |
@ConditionalOnExpression |
SpEL 表达式为 true 才加载 |
3. 实战例子
例子1:开关控制功能
java
@Configuration
@ConditionalOnProperty(
value = "feature.sms.enabled",
havingValue = "true"
)
public class SmsConfig {
@Bean
public SmsService smsService() {
return new AliyunSmsService();
}
}
配置:
yaml
feature:
sms:
enabled: true
解释:
只有
enabled=true
,短信服务才会被创建。想关掉时把true
改成false
就行!havingValue = "true"
:它的值必须是true
,我才会执行下面的代码。
例子2:有 Redis 才配 Redis
java
@Configuration
@ConditionalOnClass(RedisTemplate.class)
public class RedisConfig {
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate() {
return new RedisTemplate<>();
}
}
解释:
只有项目里有
RedisTemplate
这个类,我才加载这个Redis配置。并且如果已经有人配了
RedisTemplate
,我就不再重复配了。
4. 自定义条件:自己定规则
你还可以自己写安检规则。
比如:只有在test
环境下才加载某个测试服务。
java
public class OnTestProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String profile = context.getEnvironment().getProperty("spring.profiles.active");
return "test".equals(profile);
}
}
@Conditional(OnTestProfileCondition.class)
@Bean
public TestService testService() {
return new TestService();
}
只有当前激活的环境是test
,我才创建TestService
这个 Bean。"
拆解:
1. 先写一个"判断类":OnTestProfileCondition
它实现了Condition
接口,必须写matches()
方法 matches()
返回true
→ 条件成立,创建 Bean 返回false
→ 条件不成立,不创建
2. 在 matches()
里:
java
String profile = context.getEnvironment().getProperty("spring.profiles.active");
return "test".equals(profile);
意思是:
- 问Spring:"现在是哪个环境?"(开发?测试?生产?)
- 如果是
test
,就返回true
→ 创建 Bean - 如果是
dev
或prod
,返回false
→ 不创建
举个例子:
你有一个 TestService
,里面全是测试用的假数据、模拟接口。
- 在测试环境(
test
)→ 需要它 - 在生产环境(
prod
)→ 绝对不能有它!
用这个条件,就能确保:只在测试环境加载它。
5. 多个条件怎么组合?
Spring 还支持"或"、"与"、"非":
有时候,一个条件不够用,你想说:只要有Redis或者Kafka,我就加载某个配置。
java
static class RedisOrKafkaCondition extends AnyNestedConditions {
public RedisOrKafkaCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass(RedisOperations.class)
static class HasRedis {}
@ConditionalOnClass(KafkaTemplate.class)
static class HasKafka {}
}
拆解:
AnyNestedConditions
:意思是"任意一个成立就行"(相当于"或")
你定义两个内部类: HasRedis
:检查是否有 RedisOperations
HasKafka
:检查是否有 KafkaTemplate
只要其中一个存在,整个条件就通过
类比:
就像招聘要求:
"会 Java 或 会 Python 的人都可以报名。"
不是非要两个都会,会一个就行。
如果是必须两个都有呢?(与)
用 AllNestedConditions
:
java
static class RedisAndKafkaCondition extends AllNestedConditions {
public RedisAndKafkaCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass(RedisOperations.class)
static class HasRedis {}
@ConditionalOnClass(KafkaTemplate.class)
static class HasKafka {}
}
意思是:必须同时有 Redis 和 Kafka 才成立"(相当于"与")
三、组合使用
把@ConfigurationProperties
和@Conditional
结合,就是 Spring Boot 自动配置的核心设计模式。
举个例子:可配置的邮件服务
1. 配置文件
yaml
email:
enabled: true
host: smtp.163.com
port: 25
username: user@163.com
password: 123456
default-to: admin@example.com
2. 配置类(自动装配 + 校验)
java
@Configuration
@ConfigurationProperties(prefix = "email")
@ConditionalOnProperty(prefix = "email", name = "enabled", havingValue = "true")
@Validated
@Data
public class EmailAutoConfig {
@NotBlank
private String host;
@Min(1)
@Max(65535)
private int port = 25;
@Email
private String username;
private String password;
private String defaultTo;
@Bean
@ConditionalOnMissingBean
public JavaMailSender mailSender() {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(host);
sender.setPort(port);
sender.setUsername(username);
sender.setPassword(password);
return sender;
}
@Bean
public EmailService emailService(JavaMailSender sender) {
return new EmailService(sender, defaultTo);
}
}
3. 使用
java
@Service
public class UserService {
private final EmailService emailService;
public UserService(EmailService emailService) {
this.emailService = emailService;
}
public void register(String email) {
// 注册逻辑...
emailService.sendWelcomeEmail(email);
}
}
效果:
- 配置
email.enabled=false
→ 邮件功能完全不加载 - 配置写错(如 port=99999)→ 启动就报错,不运行
- 其他人用你的模块?引入依赖 + 写配置,直接用!
四、最佳实践
1. 能用@ConfigurationProperties
就别尽量不用@Value
→ 集中管理,支持校验、列表、嵌套。
2. 配置一定要加校验
→ 用 @Validated
+ @NotBlank
/@Min
/@Email
,防坑。
3. 用@ConditionalOnXxx
控制加载时机
→ 别让没用的 Bean 占内存。
4. 加@ConditionalOnMissingBean
防止冲突
→ 别人想自定义,也能覆盖你。
5. 写Starter时用这套组合拳
→ 别人引入你的jar,配置一下就能用,超方便!
总结
配置,是代码和环境之间的桥梁。
用好@ConfigurationProperties
和@Conditional
,你能做到:
- 配置集中管理,清晰可读
- 参数自动校验,安全可靠
- 功能按需加载,资源不浪费
- 模块可复用,开箱即用
掌握它,你就不只是会写代码,更是会设计系统的开发者!
如果你觉得这篇文章讲得清楚,欢迎点赞、收藏、转发!
公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《Java8 都出这么多年了,Optional 还是没人用?到底卡在哪了?》
《90%的人不知道!Spring官方早已不推荐@Autowired?这3种注入方式你用对了吗?》