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");
    }
}

如果不想使用文件,可以直接指定 namevalue

@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.locationspring.config.additional-location 属性包含附加配置文件,但它们有某些限制。例如,它们必须在启动应用之前定义(作为环境或系统属性,或使用命令行参数),因为它们会在进程早期使用。

在上述版本中,可以在 application.propertiesapplication.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