如何通过 Java 配置和@PropertySource在 Spring 中设置和使用属性。

1. 通过注释注册属性文件
Spring 3.1 还引入了新的 @PropertySource 注释,作为将属性源添加到环境中的便捷机制。我们可以将此注释与 @Configuration 注释结合使用:
@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
//...
}
注册新属性文件的另一种非常有用的方法是使用占位符,它允许我们在运行时动态选择正确的文件:
@PropertySource({
"classpath:persistence-${envTarget:mysql}.properties"
})
...
1.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 {
//...
}
值得注意的是,无论哪种情况,如果发生属性名称冲突,最后一个源读取优先。
2. 使用/注入属性
使用 @Value 注释注入属性非常简单:
@Value( "${jdbc.url}" )
private String jdbcUrl;
我们还可以为属性指定默认值:
@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;
Spring 3.1 中添加的新 PropertySourcesPlaceholderConfigurer 解决了 bean 定义属性值和 @Value 注释中的 ${...} 占位符。
最后,我们可以使用 环境 API 获取属性的值:
@Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));
3. Spring Boot的属性
在我们进入属性的更高级配置选项之前,让我们花一些时间看一下 Spring Boot 中的新属性支持。一般来说,与标准Spring相比,这种新的支持涉及更少的配置,这当然是Spring Boot的主要目标之一。
3.1.应用程序属性:默认属性文件
Spring 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
请注意,对于属性文件,三段表示法前面有一个注释字符 ( # )。
3.2. 特定于环境的属性文件
如果我们需要针对不同的环境,Spring Boot 中有一个内置机制。我们可以简单地在 src/main/resources 目录中定义一个 application-environment.properties 文件,然后设置一个具有相同环境名称的 Spring 配置文件。
例如,如果我们定义一个“暂存”环境,这意味着我们必须定义一个 暂存 配置文件,然后定义 application-staging.properties 。
此 env 文件将被加载,并将优先于默认属性文件 。 请注意,仍将加载默认文件,只是当发生属性冲突时,特定于环境的属性文件优先。
3.3. 测试属性文件
在测试应用程序时,我们可能还需要使用不同的属性值。Spring Boot 通过在测试运行期间查看我们的 src/test/resources 目录来为我们处理这个问题。同样,默认属性仍将正常注入,但如果发生冲突,则这些属性将被这些属性覆盖。
3.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 注释 的属性参数来实现 类似的效果:
@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");
}
}
3.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 再次应用其配置方法的约定,自动在属性名称及其相应字段之间映射。我们需要提供的只是属性前缀。
3.6. 替代方案:YAML 文件
Spring 还支持 YAML 文件。所有相同的命名规则都适用于特定于测试、特定于环境和默认属性文件。唯一的区别是文件扩展名和对类路径上的 SnakeYAML 库的依赖。YAML 特别适用于分层属性存储。
properties文件:
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 注释,因此如果我们需要使用此注释,它将限制我们使用属性文件。
另一个值得注意的一点是,在 2.4.0 版本中,Spring Boot 改变了从多文档 YAML 文件加载属性的方式。以前,它们的添加顺序基于配置文件激活顺序。但是,对于新版本,框架遵循我们之前为 .properties 文件指出的相同排序规则;文件中声明较低的属性将简单地覆盖较高的属性。
此外,在此版本中,无法再从特定于配置文件的文档激活配置文件,从而使结果更清晰、更可预测。
3.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/
注意:为了清楚起见,我们在这里使用换行符格式化此属性。
3.8. 命令行参数的属性
除了使用文件,我们还可以直接在命令行上传递属性:
java -jar app.jar --property="value"
我们也可以通过系统属性来做到这一点,这些属性在 -jar 命令之前而不是之后提供:
java -Dproperty.name="value" -jar app.jar
3.9. 来自环境变量的属性
Spring 引导还将检测环境变量,将它们视为属性:
export name=value
java -jar app.jar
3.10. 属性值的随机化
如果我们不想要确定性属性值,我们可以使用 RandomValuePropertySource 来随机化属性的值:
random.number=${random.int}
random.long=${random.long}
random.uuid=${random.uuid}
4. 使用Raw Beans配置 —PropertySourcesPlaceholderConfigurer
除了将属性获取到 Spring 中的便捷方法外,我们还可以手动定义和注册属性配置 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;
}
5. 父子上下文中的属性
这个问题一次又一次地出现:当我们的 Web 应用程序具有父上下文和子上下文时会发生什么?父上下文可能具有一些共同的核心功能和 bean,然后是一个(或多个)子上下文,可能包含特定于 servlet 的 bean。在这种情况下,定义属性文件并将其包含在这些上下文中的最佳方法是什么?以及如何最好地从 Spring 中检索这些属性?
如果文件是在父上下文中定义的:
- @Value 在子上下文中有效:是
- @Value 在父上下文中工作:是
- 环境.get属性 在子上下文中有效:是
- 环境.get 属性在 父 上下文中有效:是
如果文件是在子上下文中定义的:
- @Value 在子上下文中有效:是
- @Value 在父上下文中有效:否
- 环境.get属性 在子上下文中有效:是
- 环境.get属性 在父上下文中有效:否