1. 概述

在本文中,我们将学习如何在 Spring 应用中重新加载属性文件。Spring 提供了多种方式来读取属性,但在运行时动态更新属性并不是默认支持的功能。我们会介绍几种实现方式,包括手动实现和使用 Spring Cloud 的自动刷新机制。


2. 在 Spring 中读取属性

Spring 提供了多种方式来访问配置属性:

Environment:通过注入 Environment 实例,使用 getProperty() 方法读取属性值。Environment 包含多个属性源(如系统属性、命令行参数、application.properties 等),也可以通过 @PropertySource 添加自定义属性源。

Properties:将属性文件加载为 Properties 实例,然后通过 properties.get("key") 获取属性值。

@Value:使用 @Value("${key}") 注解直接将属性值注入到 Bean 中。

@ConfigurationProperties:适用于结构化配置,通过绑定属性前缀到一个 Java Bean 来使用。


3. 从外部文件重新加载属性

若想在运行时更新属性,最简单的方式是将配置文件放在 jar 包之外,通过启动参数 --spring.config.location=file://path-to-file 告知 Spring 配置文件的位置。

但 Spring 默认不会自动监听文件变化,我们需要手动实现或借助工具来实现自动刷新。

一个常用的工具是 Apache Commons Configuration,它提供了自动检测文件修改并重新加载的功能。

添加依赖

<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.10</version>
</dependency>

创建 PropertiesConfiguration Bean

@Bean
@ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false)
public PropertiesConfiguration propertiesConfiguration(
  @Value("${spring.config.location}") String path) throws Exception {
    String filePath = new File(path.substring("file:".length())).getCanonicalPath();
    PropertiesConfiguration configuration = new PropertiesConfiguration(new File(filePath));
    configuration.setReloadingStrategy(new FileChangedReloadingStrategy());
    return configuration;
}

这里我们使用了 FileChangedReloadingStrategy,它默认每 5 秒检查一次文件是否被修改。可以通过 setRefreshDelay(long) 设置检查间隔。


3.1. 重新加载 Environment 属性

如果我们希望通过 Environment 获取动态更新的属性值,需要自定义一个 PropertySource,并使用 PropertiesConfiguration 提供最新的值。

实现 ReloadablePropertySource

public class ReloadablePropertySource extends PropertySource {

    private final PropertiesConfiguration propertiesConfiguration;

    public ReloadablePropertySource(String name, PropertiesConfiguration propertiesConfiguration) {
        super(name);
        this.propertiesConfiguration = propertiesConfiguration;
    }

    @Override
    public Object getProperty(String key) {
        return propertiesConfiguration.getProperty(key);
    }
}

将其加入 Environment

@Configuration
public class ReloadablePropertySourceConfig {

    private final ConfigurableEnvironment env;

    public ReloadablePropertySourceConfig(@Autowired ConfigurableEnvironment env) {
        this.env = env;
    }

    @Bean
    @ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false)
    public ReloadablePropertySource reloadablePropertySource(PropertiesConfiguration properties) {
        ReloadablePropertySource ret = new ReloadablePropertySource("dynamic", properties);
        MutablePropertySources sources = env.getPropertySources();
        sources.addFirst(ret); // 保证优先级最高
        return ret;
    }
}

使用示例

@Component
public class EnvironmentConfigBean {

    private final Environment environment;

    public EnvironmentConfigBean(@Autowired Environment environment) {
        this.environment = environment;
    }

    public String getColor() {
        return environment.getProperty("application.theme.color");
    }
}

⚠️ 注意:我们把自定义的 PropertySource 放在首位,确保它能覆盖其他同名属性。


3.2. 重新加载 Properties 实例

如果你使用的是 Properties 实例,可以继承 Properties 并重写 getProperty 方法:

public class ReloadableProperties extends Properties {
    private final PropertiesConfiguration propertiesConfiguration;

    public ReloadableProperties(PropertiesConfiguration propertiesConfiguration) throws IOException {
        super.load(new FileReader(propertiesConfiguration.getFile()));
        this.propertiesConfiguration = propertiesConfiguration;
    }

    @Override
    public String getProperty(String key) {
        String val = propertiesConfiguration.getString(key);
        super.setProperty(key, val);
        return val;
    }
}

然后将其注册为 Bean 即可。


3.3. 使用 @ConfigurationProperties 刷新 Bean

@ConfigurationProperties 默认绑定的是静态 Bean。若想实现动态刷新,需要配合 Spring Cloud 的 @RefreshScope 使用。

⚠️ 注意:Spring 默认的 Bean 作用域是 singleton@RefreshScope 会将其改为 refresh,在属性更新后重新创建 Bean。


3.4. 使用 @Value 刷新 Bean

@ConfigurationProperties 类似,@Value 也受作用域限制。若想实现刷新,也需要配合 @RefreshScope 使用。


4. 使用 Actuator 和 Spring Cloud 实现刷新

Spring Boot Actuator 提供了 /actuator/refresh 接口,可以触发属性刷新。但这个功能依赖于 Spring Cloud。

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter</artifactId>
</dependency>

并在 pom.xml 中引入 Spring Cloud 版本管理:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

启用 refresh 接口

management.endpoints.web.exposure.include=refresh

调用 /actuator/refresh 后,Spring 会重新加载所有属性源,并触发 EnvironmentChangeEvent


4.1. 使用 @ConfigurationProperties + @RefreshScope

@Component
@ConfigurationProperties(prefix = "application.theme")
@RefreshScope
public class ConfigurationPropertiesRefreshConfigBean {
    private String color;

    public void setColor(String color) {
        this.color = color;
    }

    // getter
}

⚠️ 注意:@ConfigurationProperties 要求必须有 setter 方法。


4.2. 使用 @Value + @RefreshScope

@Component
@RefreshScope
public class ValueRefreshConfigBean {
    private String color;

    public ValueRefreshConfigBean(@Value("${application.theme.color}") String color) {
        this.color = color;
    }

    // getter
}

⚠️ 注意:如果 Bean 被显式标记为 @Scope("singleton")/actuator/refresh 将不会生效。


5. 总结

本文介绍了多种在 Spring 中实现属性动态刷新的方式:

✅ 手动实现:通过 PropertiesConfiguration + 自定义 PropertySource 实现属性文件监听和更新。

✅ 使用 Spring Cloud:通过 @RefreshScope + /actuator/refresh 实现自动刷新,适用于 @Value@ConfigurationProperties

⚠️ 注意事项:

  • 不同作用域的 Bean 行为不同,singleton 不会自动刷新。
  • @ConfigurationProperties 必须提供 setter 方法。
  • @Value 构造注入的方式在刷新时不会重新执行构造函数,需谨慎使用。

通过上述方法,你可以根据项目需求选择合适的方式来实现属性的动态更新。


原始标题:Reloading Properties Files in Spring | Baeldung