Spring 与 Spring Boot 中的属性配置(Properties)

更新于 2025-12-30

Eugen Paraschiv 2013-10-15

1. 概述

本教程将展示如何在 Spring 中通过 Java 配置和 @PropertySource 注解来设置和使用属性(properties)。

我们还将了解 Spring Boot 中属性的工作方式。

2. 通过注解注册属性文件

Spring 3.1 引入了新的 @PropertySource 注解,作为向环境(Environment)中添加属性源的便捷机制。

我们可以将此注解与 @Configuration 注解结合使用:

@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
    //...
}

另一种非常有用的方式是使用占位符,这样可以在运行时动态选择正确的属性文件:

@PropertySource({ 
  "classpath:persistence-${envTarget:mysql}.properties"
})
...

2.1 定义多个属性文件位置

根据 Java 8 的规范,@PropertySource 注解是可重复的(repeatable)。因此,如果我们使用的是 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 需要的配置更少,这当然也是 Spring Boot 的主要目标之一。

4.1 application.properties:默认属性文件

Boot 对属性文件采用了“约定优于配置”的方式。这意味着我们只需在 src/main/resources 目录下放置一个 application.properties 文件,它就会被自动检测到。之后,我们可以像平常一样注入其中加载的任意属性。

因此,使用这个默认文件时,无需显式注册 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 支持多文档属性文件(multi-document properties files),类似于 YAML 文件的原生支持:

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

注意:对于 .properties 文件,三连横线(---)前必须加上注释符号 #

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 层次化属性(Hierarchical Properties)

如果属性是分组的,我们可以使用 @ConfigurationProperties 注解,将这些层次化的属性映射为 Java 对象图。

例如,用于配置数据库连接的属性:

database.url=jdbc:postgresql://localhost:5432/instance
database.username=foo
database.password=bar

然后使用注解将其映射到一个 Java 对象:

@ConfigurationProperties(prefix = "database")
public class Database {
    String url;
    String username;
    String password;

    // 标准的 getter 和 setter
}

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 注解。因此,如果需要使用该注解,则必须使用 .properties 文件。

另外,从 Spring Boot 2.4.0 开始,框架改变了从多文档 YAML 文件加载属性的方式。此前,属性加载顺序依赖于 profile 的激活顺序;而在新版本中,框架遵循与 .properties 文件相同的规则——文件中靠后的属性会覆盖靠前的同名属性

此外,从该版本开始,不能再从 profile-specific 文档中激活 profile,从而使行为更加清晰和可预测。

4.7 导入额外的配置文件

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

从 2.4.0 开始,我们可以在 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

除了上述便捷方式,我们也可以手动定义并注册属性配置 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 应用具有父上下文和子上下文时(例如父上下文包含通用核心功能,子上下文包含 Servlet 特定的 Bean),应该如何定义和加载属性文件?又该如何从 Spring 中获取这些属性?

以下是简明总结:

如果属性文件定义在父上下文中:

  • @Value 在子上下文中:✅ 可用
  • @Value 在父上下文中:✅ 可用
  • environment.getProperty() 在子上下文中:✅ 可用
  • environment.getProperty() 在父上下文中:✅ 可用

如果属性文件定义在子上下文中:

  • @Value 在子上下文中:✅ 可用
  • @Value 在父上下文中:❌ 不可用
  • environment.getProperty() 在子上下文中:✅ 可用
  • environment.getProperty() 在父上下文中:❌ 不可用

7. 结论

本文展示了在 Spring 中使用属性和属性文件的多种示例,涵盖了从基础配置到 Spring Boot 高级特性的内容。