一、Spring Boot 配置体系的定位
Spring Boot 的设计哲学是约定大于配置 ,但当默认行为无法满足需求时,就需要通过外部化配置来覆盖。配置文件(application.yml / application.properties)是 Spring Boot 应用与外部环境交互的核心接口,承载着数据库连接、第三方服务密钥、功能开关、业务参数等所有可变配置。
Spring Boot 的配置体系不是简单的"读文件",而是一个多源合并 → 优先级裁决 → 类型转换 → 对象绑定 的完整工程。理解这个链路,才能写出既简洁又健壮的配置代码。
二、配置文件加载顺序与优先级
Spring Boot 启动时会按固定顺序扫描多个位置、多种格式的配置文件,形成优先级队列:
2.1 配置文件位置优先级(由高到低)
1. jar 包外部的 ./config/application.yml
2. jar 包外部的 ./application.yml
3. jar 包内部的 classpath:/config/application.yml
4. jar 包内部的 classpath:/application.yml
位置越靠前,优先级越高。这意味着外部配置可以覆盖 jar 包内部的默认配置,这是生产环境部署的关键机制------把 jar 包和配置文件分离,无需重新打包就能修改配置。
2.2 配置格式优先级
同一位置下,properties 的优先级高于 yml。如果同时存在 application.properties 和 application.yml,前者会覆盖后者的同名配置。
2.3 运行期配置源的完整优先级(由高到低)
Spring Boot 的 Environment 对象维护了一个 PropertySource 列表,最终的优先级如下:
1. 命令行参数(--server.port=8081)
2. 来自 java:comp/env 的 JNDI 属性
3. 系统环境变量(SERVER_PORT=8081)
4. 系统属性(System.getProperties())
5. RandomValuePropertySource(random.int、random.uuid)
6. jar 外部的 profile-specific 配置文件
7. jar 内部的 profile-specific 配置文件
8. jar 外部的 application.yml
9. jar 内部的 application.yml
10. @PropertySource 注解加载的配置
11. 默认属性(SpringApplication.setDefaultProperties)
这个优先级列表决定了当同一个 key 出现在多个源时,哪个值最终生效 。最常用的规则是:命令行参数 > 环境变量 > 配置文件 > 默认值。
三、@ConfigurationProperties(批量结构化绑定)
@ConfigurationProperties 是 Spring Boot 的专属注解,用于将一组相关配置批量绑定到一个 Java 对象(POJO)。
3.1 基本用法
java
@Component
@ConfigurationProperties(prefix = "app.mail")
@Data
public class MailProperties {
private String host;
private int port;
private String username;
private String password;
}
对应配置:
app:
mail:
host: smtp.example.com
port: 587
username: noreply@example.com
password: secret123
Spring Boot 启动时,扫描 app.mail 前缀下的所有 key,按照松散绑定规则映射到对象的字段。
3.2 松散绑定(Relaxed Binding)
Spring Boot 在匹配配置 key 和 Java 字段名时,采用松散绑定策略,不严格要求字符完全一致:
|-------------------------|--------------|
| 配置文件中的写法 | 匹配的 Java 字段名 |
| app.mail.host-name | hostName |
| app.mail.hostName | hostName |
| app.mail.host_name | hostName |
| APP_MAIL_HOSTNAME(环境变量) | hostName |
转换规则:
去掉 prefix 前缀。
短横线 -、下划线 _、大写环境变量格式,都会统一转换为驼峰命名。
不区分大小写。
3.3 类型转换
配置文件中的值本质都是字符串,但 Java 字段可以是任意类型。Spring Boot 的 ConversionService 会自动处理转换:
java
@Data
public class ServerProperties {
private int port; // "8080" → 8080
private boolean sslEnabled; // "true" → true
private Duration timeout; // "30s" → Duration.ofSeconds(30)
private DataSize maxFileSize; // "10MB" → DataSize.ofMegabytes(10)
private List<String> allowedOrigins; // "a,b,c" → ["a","b","c"]
private Map<String, String> headers; // "k1:v1,k2:v2" → Map
}
3.4 嵌套对象绑定
当配置具有层级结构时,可以用嵌套 POJO 接收:
java
@Data
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private Mail mail; // 嵌套对象
private Database database; // 嵌套对象
}
@Data
public class Mail {
private String host;
private int port;
}
@Data
public class Database {
private String url;
private String username;
}
对应配置:
java
app:
name: my-application
mail:
host: smtp.example.com
port: 587
database:
url: jdbc:mysql://localhost:3306/db
username: root
3.5 配合 JSR-303 校验
配置类可以加上 @Validated,对字段添加校验注解,启动时如果配置值不合法,直接报错:
java
@Component
@ConfigurationProperties(prefix = "app.mail")
@Validated
@Data
public class MailProperties {
@NotBlank(message = "邮件服务器地址不能为空")
private String host;
@Min(1)
@Max(65535)
private int port;
@Pattern(regexp = "^[A-Za-z0-9+_.-]+@(.+)$", message = "邮箱格式不正确")
private String username;
}
启动时如果 app.mail.port = 99999,Spring Boot 会抛 BindValidationException,阻止应用启动。
3.6 不可变配置:@ConstructorBinding
如果不希望配置类是可变的(不想生成 setter,希望字段为 final),可以用 @ConstructorBinding:
java
@ConfigurationProperties(prefix = "app.mail")
@ConstructorBinding
@Getter
public class MailProperties {
private final String host;
private final int port;
private final String username;
public MailProperties(String host, int port, String username) {
this.host = host;
this.port = port;
this.username = username;
}
}
注意:使用 @ConstructorBinding 时,类上不能加 @Component,需要通过 @EnableConfigurationProperties(MailProperties.class) 在配置类中显式注册。
3.7 第三方库配置类的启用方式
如果配置类来自第三方 jar 包(无法修改源码加 @Component),可以用 @EnableConfigurationProperties:
java
@Configuration
@EnableConfigurationProperties({RedisProperties.class, KafkaProperties.class})
public class MyConfig {
}
四、@Value(单值注入)
@Value 是 Spring Framework 的原生注解,用于将单个配置值注入到字段、方法参数或构造器参数。
4.1 基本用法
java
@Component
public class SomeService {
@Value("${app.mail.host}")
private String mailHost;
@Value("${app.mail.port}")
private int mailPort;
}
4.2 默认值
如果配置项可能不存在,可以用冒号指定默认值:
java
@Value("${app.mail.port:587}")
private int mailPort; // 如果没配置,默认 587
@Value("${app.feature.enabled:false}")
private boolean featureEnabled; // 默认 false
4.3 SpEL 表达式
@Value 支持 Spring Expression Language(SpEL),这是它比 @ConfigurationProperties 强大的地方:
java
// 算术运算
@Value("#{10 * 60}")
private int timeoutSeconds;
// 调用静态方法
@Value("#{T(java.time.Instant).now().toEpochMilli()}")
private long startupTime;
// 引用其他 Bean 的属性
@Value("#{mailProperties.host}")
private String mailHost;
// 条件表达式
@Value("#{systemProperties['os.name'].contains('Windows') ? 'C:/temp' : '/tmp'}")
private String tempDir;
// 随机值
@Value("${random.uuid}")
private String traceId;
@Value("${random.int(1000,9999)}")
private int randomCode;
4.4 @Value 的局限性
|---------|--------------------------------------------------------|
| 局限 | 说明 |
| 不支持松散绑定 | @Value("${app.mail.hostName}") 不能匹配 app.mail.host-name |
| 不支持校验 | 无法配合 @Validated 对注入值做校验 |
| 不支持复杂对象 | 不能直接将一组配置绑定为嵌套对象 |
| 逐个注入繁琐 | 10 个字段需要 10 个 @Value 注解 |
选型建议:
配置项成组出现、需要结构化、需要校验 → 用 @ConfigurationProperties
只需要单个值、需要SpEL、需要默认值 → 用 @Value