1. 概述
本文将演示如何通过 Java 配置和 @PropertySource 注解在 Spring 中设置和使用属性。
我们也会探讨 Spring Boot 中属性的运作机制。
2. 通过注解注册属性文件
Spring 3.1 引入了新的 @PropertySource 注解,作为向环境添加属性源的便捷机制。
我们可以将此注解与 @Configuration 注解结合使用:
@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
//...
}
另一种实用的注册方式是使用占位符,允许在运行时动态选择正确的文件:
@PropertySource({
"classpath:persistence-${envTarget:mysql}.properties"
})
...
2.1. 定义多个属性位置
根据 Java 8 约定,*@PropertySource* 注解是可重复的。因此,如果使用 Java 8 或更高版本,我们可以用该注解定义多个属性位置:
@PropertySource("classpath:foo.properties")
@PropertySource("classpath:bar.properties")
public class PropertiesWithJavaConfig {
//...
}
当然,也可以使用 @PropertySources 注解并指定 @PropertySource 数组。这种方式适用于所有支持的 Java 版本,不仅限于 Java 8+:
@PropertySources({
@PropertySource("classpath:foo.properties"),
@PropertySource("classpath:bar.properties")
})
public class PropertiesWithJavaConfig {
//...
}
⚠️ 注意:无论哪种方式,当属性名冲突时,后读取的源会覆盖先前的值。
3. 使用/注入属性
通过 @Value 注解 注入属性非常直接:
@Value( "${jdbc.url}" )
private String jdbcUrl;
我们也可以为属性指定默认值:
@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;
Spring 3.1 新增的 PropertySourcesPlaceholderConfigurer 会解析 bean 定义属性值和 @Value 注解中的 ${…} 占位符。
最后,还可以使用 Environment API 获取属性值:
@Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));
4. Spring Boot 中的属性
在深入高级配置选项前,我们先了解 Spring Boot 对属性的新支持。
总体而言,相比标准 Spring,这种新支持需要的配置更少,这当然是 Boot 的主要目标之一。
4.1. application.properties:默认属性文件
Boot 对属性文件采用其典型的约定优于配置方法。这意味着只需将 application.properties 文件放在 src/main/resources 目录下,它就会被自动检测到。然后可以正常注入其中加载的任何属性。
因此,使用这个默认文件时,无需显式注册 PropertySource,甚至无需提供属性文件路径。
如果需要,也可以在运行时通过环境属性配置不同文件:
java -jar app.jar --spring.config.location=classpath:/another-location.properties
从 Spring Boot 2.3 开始,还支持配置文件的通配符位置。
例如,可将 spring.config.location 设为 *config/*/*:
java -jar app.jar --spring.config.location=config/*/
这样,Spring Boot 会在 jar 包外查找匹配 config/*/ 目录模式的配置文件。当有多个配置属性源时,这个功能特别实用。
从版本 2.4.0 起,Spring Boot 支持使用多文档属性文件,类似 YAML 的原生设计:
baeldung.customProperty=defaultValue
#---
baeldung.customProperty=overriddenValue
注意:对于属性文件,三横线记号前需要加注释符(*#*)。
4.2. 环境特定的属性文件
如果需要针对不同环境,Boot 提供了内置机制。
只需在 src/main/resources 目录中定义 application-environment.properties 文件,然后设置同名的 Spring Profile。
例如,定义 "staging" 环境时,需创建 staging Profile 和 application-staging.properties 文件。
该环境文件将被加载并覆盖默认属性文件中的同名属性。注意:默认文件仍会被加载,只是当属性冲突时,环境特定文件优先。
4.3. 测试专用的属性文件
测试时可能需要使用不同的属性值。
Spring Boot 在测试运行时会自动查找 src/test/resources 目录。默认属性仍可正常注入,但冲突时会被测试属性覆盖。
4.4. @TestPropertySource 注解
如果需要更精细地控制测试属性,可使用 @TestPropertySource 注解。
它允许为特定测试上下文设置测试属性,优先级高于默认属性源:
@RunWith(SpringRunner.class)
@TestPropertySource("/foo.properties")
public class FilePropertyInjectionUnitTest {
@Value("${foo}")
private String foo;
@Test
public void whenFilePropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}
若不想使用文件,可直接指定名称和值:
@RunWith(SpringRunner.class)
@TestPropertySource(properties = {"foo=bar"})
public class PropertyInjectionUnitTest {
@Value("${foo}")
private String foo;
@Test
public void whenPropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}
也可使用 @SpringBootTest 注解的 properties 参数实现类似效果:
@RunWith(SpringRunner.class)
@SpringBootTest(
properties = {"foo=bar"}, classes = SpringBootPropertiesTestApplication.class)
public class SpringBootPropertyInjectionIntegrationTest {
@Value("${foo}")
private String foo;
@Test
public void whenSpringBootPropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}
4.5. 层次化属性
对于分组属性,可利用 @ConfigurationProperties 注解,将属性层次结构映射到 Java 对象图。
以数据库连接配置属性为例:
database.url=jdbc:postgresql:/localhost:5432/instance
database.username=foo
database.password=bar
使用注解将其映射到数据库对象:
@ConfigurationProperties(prefix = "database")
public class Database {
String url;
String username;
String password;
// standard getters and setters
}
Spring Boot 再次应用约定优于配置,自动映射属性名与对应字段。我们只需提供属性前缀。
想深入了解配置属性,可参考我们的详细文章。
4.6. 替代方案:YAML 文件
Spring 也支持 YAML 文件。
测试专用、环境专用和默认属性文件的命名规则相同,唯一区别是文件扩展名和类路径中需要 SnakeYAML 依赖。
YAML 特别适合存储层次化属性;以下属性文件:
database.url=jdbc:postgresql:/localhost:5432/instance
database.username=foo
database.password=bar
secret: foo
等同于以下 YAML 文件:
database:
url: jdbc:postgresql:/localhost:5432/instance
username: foo
password: bar
secret: foo
值得注意的是,YAML 文件不支持 @PropertySource 注解,因此需要使用该注解时,只能选择属性文件。
另一个要点是:Spring Boot 2.4.0 版本改变了从多文档 YAML 文件加载属性的方式。之前加载顺序基于 Profile 激活顺序,而新版本遵循与 .properties 文件相同的排序规则——文件中靠后声明的属性会覆盖靠前的属性。
此外,该版本中 Profile 无法再从 Profile 特定文档中激活,使结果更清晰可预测。
4.7. 导入额外配置文件
在 2.4.0 版本之前,Spring Boot 允许使用 spring.config.location 和 spring.config.additional-location 属性包含额外配置文件,但存在限制。例如,它们必须在应用启动前定义(作为环境变量、系统属性或命令行参数),因为它们在流程早期就被使用。
在上述版本中,可在 application.properties 或 application.yml 文件中使用 spring.config.import 属性轻松包含额外文件。该属性支持以下特性:
- 添加多个文件或目录
- 文件可从类路径或外部目录加载
- 指定文件未找到时是否启动失败,或设为可选文件
- 导入无扩展名文件
有效示例:
spring.config.import=classpath:additional-application.properties,
classpath:additional-application[.yml],
optional:file:./external.properties,
classpath:additional-application-properties/
注意:此处为清晰起见使用换行符格式化。
Spring 会将导入视为插入到导入声明下方的新文档。
4.8. 通过命令行参数传递属性
除文件外,还可直接在命令行传递属性:
java -jar app.jar --property="value"
也可通过系统属性传递(需在 -jar 命令前指定):
java -Dproperty.name="value" -jar app.jar
4.9. 通过环境变量传递属性
Spring Boot 会检测环境变量并将其视为属性:
export name=value
java -jar app.jar
4.10. 属性值随机化
若需要非确定性属性值,可使用 RandomValuePropertySource 随机化属性值:
random.number=${random.int}
random.long=${random.long}
random.uuid=${random.uuid}
4.11. 其他类型的属性源
Spring Boot 支持多种属性源,通过精心设计的排序实现合理的覆盖机制。建议查阅官方文档,其内容超出本文范围。
5. 使用原始 Bean 配置 — PropertySourcesPlaceholderConfigurer
除便捷的属性注入方式外,也可手动定义和注册属性配置 Bean。
使用 PropertySourcesPlaceholderConfigurer 可完全控制配置,但缺点是更冗长,且多数情况下非必需。
看如何通过 Java 配置定义该 Bean:
@Bean
public static PropertySourcesPlaceholderConfigurer properties(){
PropertySourcesPlaceholderConfigurer pspc
= new PropertySourcesPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[ ]
{ new ClassPathResource( "foo.properties" ) };
pspc.setLocations( resources );
pspc.setIgnoreUnresolvablePlaceholders( true );
return pspc;
}
6. 父子上下文中的属性
这个经典问题常被问及:当Web 应用存在父子上下文时会发生什么?父上下文可能包含通用核心功能和 Bean,子上下文(可能多个)则包含 Servlet 特定 Bean。
这种情况下,最佳实践是什么?如何从 Spring 中高效获取这些属性?
简单总结如下:
如果文件定义在父上下文:
@Value 在子上下文有效:✅ 是
@Value 在父上下文有效:✅ 是
environment.getProperty 在子上下文有效:✅ 是
environment.getProperty 在父上下文有效:✅ 是
如果文件定义在子上下文:
@Value 在子上下文有效:✅ 是
@Value 在父上下文有效:❌ 否
environment.getProperty 在子上下文有效:✅ 是
environment.getProperty 在父上下文有效:❌ 否
7. 总结
本文展示了在 Spring 中使用属性和属性文件的多种方式。
完整代码示例可在 GitHub 获取。