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 的原生设计

baeldung.customProperty=defaultValue
#---
baeldung.customProperty=overriddenValue

注意:对于属性文件,三横线记号前需要加注释符(*#*)。

4.2. 环境特定的属性文件

如果需要针对不同环境,Boot 提供了内置机制。

只需在 src/main/resources 目录中定义 application-environment.properties 文件,然后设置同名的 Spring Profile

例如,定义 "staging" 环境时,需创建 staging Profile 和 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

使用注解将其映射到数据库对象:

@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 注解,因此需要使用该注解时,只能选择属性文件。

另一个要点是:Spring Boot 2.4.0 版本改变了从多文档 YAML 文件加载属性的方式。之前加载顺序基于 Profile 激活顺序,而新版本遵循与 .properties 文件相同的排序规则——文件中靠后声明的属性会覆盖靠前的属性。

此外,该版本中 Profile 无法再从 Profile 特定文档中激活,使结果更清晰可预测。

4.7. 导入额外配置文件

在 2.4.0 版本之前,Spring Boot 允许使用 spring.config.locationspring.config.additional-location 属性包含额外配置文件,但存在限制。例如,它们必须在应用启动前定义(作为环境变量、系统属性或命令行参数),因为它们在流程早期就被使用。

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

除便捷的属性注入方式外,也可手动定义和注册属性配置 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 获取。


原始标题:在 Spring和Spring Boot 中使用 Properties 文件

« 上一篇: Spring中的REST分页
» 下一篇: RestTemplate Basic认证