Spring 和 Spring Boot 中的属性配置
1、概览
本文将带你了解如何通过 Java 配置和 @PropertySource
在 Spring 中设置和使用 Properties,以及 Properties 在 Spring Boot 中的工作原理。
2、通过注解注册 Properties 文件
Spring 3.1 引入了新的 @PropertySource
注解,作为一种方便的机制,用于将属性源添加到环境中。
可以将此注解与 @Configuration
注解结合使用:
@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
//...
}
另一种非常有用的注册新 Properties 文件的方法是使用占位符,它允许在运行时动态选择不同的文件:
@PropertySource({
"classpath:persistence-${envTarget:mysql}.properties"
})
...
2.1、定义多个属性位置
@PropertySource
注解是可重复的(Java 8 的可重复注解特性)。因此,如果使用的是 Java 8 或更高版本,就可以使用此注解来定义多个属性位置:
@PropertySource("classpath:foo.properties")
@PropertySource("classpath:bar.properties")
public class PropertiesWithJavaConfig {
//...
}
当然,也可以使用 @PropertySources
注解并指定一个 @PropertySource
数组。这不仅适用于 Java 8 或更高版本,也适用于任何受支持的 Java 版本:
@PropertySources({
@PropertySource("classpath:foo.properties"),
@PropertySource("classpath:bar.properties")
})
public class PropertiesWithJavaConfig {
//...
}
无论哪种情况,如果出现属性名称冲突,则以最后读取的源名称为准。
3、使用/注入 Properties
使用 @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 和 Properties
4.1、application.properties:默认的属性文件
Srping Boot 对 Properties 文件采用了典型的约定大于配置的方法。这意味着只需在 src/main/resources
目录中放入一个 application.properties
文件,它就会被自动检测到。然后,就可以从中注入任何已加载的属性。
因此,通过使用该默认文件,无需明确注册 PropertySource
,甚至无需提供 Properties 文件的路径。
如果需要,还可以在运行时使用环境属性配置不同的文件:
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
注意,对于 Properties 文件,三短横线符号前有一个注释字符(#
)。
4.2、特定环境的属性文件
Spring Boot 有一个内置的机制,可以针对不同的环境。
只需在 src/main/resources
目录中定义一个 application-environment.properties
文件,然后用相同的环境(environment)名称设置 Spring Profile 即可。
例如,假设定义了一个 “staging” 环境,这意味着必须定义一个 staging profile,然后定义 application-staging.properties
。
该属性文件将被加载,并优先于默认属性文件。注意,默认文件仍将加载,只是在出现属性冲突时,特定环境的属性文件优先。
4.3、测试专用属性文件
在测试应用时,可能还需要使用不同的属性值。
Spring Boot 会在测试运行期间查找 src/test/resources
目录,来处理这个问题。同样,默认属性仍可正常注入,但如果发生冲突,将被这些属性覆盖。
4.4、TestPropertySource 注解
如果需要对测试属性进行更精细的控制,则可以使用 @TestPropertySource
注解。
这允许为特定测试上下文(Test Context)设置测试属性,优先于默认属性源:
@RunWith(SpringRunner.class)
@TestPropertySource("/foo.properties")
public class FilePropertyInjectionUnitTest {
@Value("${foo}")
private String foo;
@Test
public void whenFilePropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}
如果不想使用文件,可以直接指定 name 和 value:
@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
然后,使用注解将它们映射到 Database
对象:
@ConfigurationProperties(prefix = "database")
public class Database {
String url;
String username;
String password;
// get、set 方法省略
}
Spring Boot 再次采用了约定重于配置的方法,只需要提供属性前缀,框架会自动映射属性名称和相应字段。
如果你想深入了解配置属性,可以参阅 这篇文章。
4.6、替代方案:YAML 文件
Spring 还支持 YAML 文件。
所有相同的命名规则都适用于特定测试、特定环境和默认属性文件。唯一的区别是文件扩展名和 classpath 上是否有 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
注解,因此如果需要使用该注解,就必须使用 Properties 文件。
另一个值得注意的是,在 2.4.0 版本中,Spring Boot 改变了从多文档 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
属性,轻松包含其他文件。该属性支持一些有趣的功能:
- 添加多个文件或目录
- 文件可以从 classpath 或外部目录中加载
- 指示启动过程在找不到文件时是否应该失败(可选文件)
- 导入无扩展名文件
一个例子:
spring.config.import=classpath:additional-application.properties,
classpath:additional-application[.yml],
optional:file:./external.properties,
classpath:additional-application-properties/
注意:为了清晰起见,这里使用了换行符。
Spring 会将导入视为一个新文档,紧接着插入到 import
声明下方。
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
除了将属性导入 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;
}
6、父子 Context 中的属性
这个问题经常看到:当 Web 应用有一个父级 Context 和一个子级 Context 时会发生什么?父级 Context 可能有一些通用的核心功能和 Bean,然后是一个(或多个)子级 Context,其中可能包含特定于服务的 Bean。
在这种情况下,定义 Properties 文件并将其包含在这些 Context 中的最佳方法是什么?从 Spring 获取这些属性的最佳方法是什么?
来做个清晰的对比,你就知道了。
如果文件是在父级 Context 中定义的:
@Value
可在子级 Context 中使用:YES@Value
可在父级 Context 中使用:YES- 在子级 Context 中
environment.getProperty
:YES - 在父级 Context 中
environment.getProperty
:YES
如果文件是在子级 Context 中定义的:
@Value
可在子级 Context 中使用:YES@Value
可在父级 Context 中使用:NO- 在子级 Context 中
environment.getProperty
:YES - 在父级 Context 中
environment.getProperty
:NO
7、总结
本文介绍了如何在 Spring 和 Spring Boot 中定义、使用 Properties。
Ref:https://www.baeldung.com/properties-with-spring