Contents

在 Spring 中使用属性和属性文件

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

blogdemo.customProperty=defaultValue
#---
blogdemo.customProperty=overriddenValue

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

4.2. 环境特定的属性文件

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

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

例如,如果我们定义一个“staging”环境,这意味着我们必须定义一个staging配置文件,然后定义application-staging.properties

此 env 文件将被加载,**并将优先于默认属性文件。**注意还是会加载默认文件,只是当发生属性冲突时,环境特定的属性文件优先。

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

另一个值得注意的点是,在 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 会将导入视为紧接在导入声明下方插入的新文档。

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. 上下文中的属性

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

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

我们将给出一个简单的细分。 如果文件是在父上下文中定义的

  • @Value子上下文中工作:是
  • @Value父上下文中工作:是
  • 子上下文中environment.getProperty:是
  • 父上下文中environment.getProperty:是

如果文件在子上下文中定义

  • @Value子上下文中工作:是
  • @Value父上下文中工作:否
  • 子上下文中environment.getProperty:是
  • 父上下文中environment.getProperty:否