@Value 与外部化配置 PropertySource
引言
把 URL、开关、页面大小等参数写死在代码里,改一次就要重新编译。Spring 用 Environment + PropertySource 抽象把配置外部化:属性来自系统环境、JVM 参数或属性文件,Bean 用 @Value("${...}") 读取。本章讲属性来源的注册与读取。
核心概念
- Environment(ConfigurableEnvironment):按优先级管理一组
PropertySource(属性来源)。context.getEnvironment().getProperty("key")可取任意来源的值。 @PropertySource(6.x 为 org.springframework.context.annotation):把一个.properties文件注册成 PropertySource,通常标在@Configuration类上。@Value:字段/参数上@Value("${key}")取属性,@Value("${key:default}")带默认值;#{...}则走 SpEL(第 18 章)。- 占位符解析器:
${...}由PropertySourcesPlaceholderConfigurer(实现 BeanFactoryPostProcessor)负责解析,需要显式注册为一个静态@Bean(Boot 应用会自动注册)。 - 优先级示例(由高到低,以官方文档为准):命令行/JVM 参数 → 系统环境变量 → 外部配置文件 → classpath 配置文件,后注册的 PropertySource 排前面。
代码示例
// 配置文件 app.properties
// report.page-size=20
// report.title=日报
@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {
// PropertySourcesPlaceholderConfigurer 是 BeanFactoryPostProcessor,
// @Configuration 里必须用 static 方法提前实例化,${...} 才能解析
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Component
public class ReportProperties {
@Value("${report.page-size:10}") // 缺省兜底 10
private int pageSize;
@Value("${report.title}") // 缺失且无默认值 → 启动报错
private String title;
// 也可以直接注入 Environment 编程读取
private final org.springframework.core.env.Environment env;
public ReportProperties(Environment env) { this.env = env; }
public String resolve(String key) { return env.getProperty(key); }
}
注意点
- 纯 Spring(非 Boot)注解环境下,忘了注册
PropertySourcesPlaceholderConfigurer时@Value不会报错,而是注入字面量${report.title}——这是经典坑。 - 值缺失又没写默认值,容器启动会因无法解析占位符直接失败,比运行期才发现好。
@PropertySource默认用系统默认编码读文件,中文属性建议用 UTF-8 文件并在 Spring Boot 外自行留意(官方推荐 properties 用 ASCII/Unicode 转义或改 UTF-8 支持)。@Value注入的是“字符串转换后的类型”,int/boolean 等基础类型 Spring 自动转换;复杂类型转换需ConversionService。- 同 key 多来源时按 PropertySource 顺序取第一个命中值,别靠猜,用
env.getPropertySources()排查。
小结
外部化配置 = Environment 统一管理 PropertySource + @PropertySource 挂载文件 + @Value("${key:default}") 读取。记得注册占位符解析器;Spring Boot 在 application.properties 上把这套机制做成了默认行为,理解本章即可无痛迁移。