Spring和Spring Boot中的属性

2023/05/12

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的设计:

tuyucheng.customProperty=defaultValue
#---
tuyucheng.customProperty=overriddenValue

请注意,对于属性文件,三破折号表示法前面有一个注释字符(#)。

4.2 特定于环境的属性文件

如果我们需要针对不同的环境,Boot中有一个内置机制。

我们可以简单的在src/main/resources目录下定义一个application-environment.properties文件,然后设置一个具有相同环境名称的Spring配置文件

例如,如果我们定义一个“staging”环境,这意味着我们必须先定义一个staging配置文件,然后再定义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

然后让我们使用注解将它们映射到Database对象:

@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注解,因此如果我们需要使用这个注解,它会限制我们使用属性文件。

另一个值得注意的地方是,在2.4.0版本中,Spring Boot改变了从多文档YAML文件加载属性的方式。以前,它们的添加顺序基于配置文件激活顺序;然而,对于新版本,框架遵循我们之前为.properties文件指出的相同排序规则;在文件中声明较低优先级的属性将简单地覆盖较高的属性。

此外,在此版本中,无法再从特定于配置文件的文档中激活配置文件,从而使结果更加清晰和可预测。

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会将导入视为一个新文档,插入到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. 使用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;
}

6. 父子上下文中的属性

这个问题一次又一次地出现:当我们的Web应用程序具有一个父上下文和一个子上下文时会发生什么?父上下文可能有一些共同的核心功能和bean,然后是一个(或多个)子上下文,可能包含特定于Servlet的bean。

在这种情况下,定义属性文件并将它们包含在这些上下文中的最佳方法是什么?以及如何最好地从Spring检索这些属性?

我们将给出一个简单的细分。

如果文件是在父上下文中定义的:

  • @Value在子上下文中工作:是

  • @Value在父上下文中工作:是

  • 子上下文中的environment.getProperty:是

  • 父上下文中的environment.getProperty:是

如果文件是在子上下文中定义的:

  • @Value在子上下文中工作:是

  • @Value在父上下文中工作:否

  • 子上下文中的environment.getProperty:是

  • 父上下文中的environment.getProperty:否

7. 总结

本文演示了几个在Spring中使用属性和属性文件的示例。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章