知識庫 / Spring / Spring Cloud RSS 訂閱

Spring 屬性文件重新加載

Spring Cloud
HongKong
9
01:19 PM · Dec 06 ,2025

1. 概述

在本教程中,我們將學習如何在 Spring 應用中重新加載屬性。

2. 在 Spring 中讀取屬性

我們有多種選項可以訪問 Spring 中的屬性:

  1. Environment — 可以通過注入 Environment 並使用 Environment#getProperty 讀取指定屬性。 Environment 包含不同的屬性源,例如系統屬性、-D 參數和 application.properties (.yml) 文件。 還可以使用 @PropertySource 添加額外的屬性源到 Environment 中。
  2. Properties — 可以將屬性文件加載到 Properties 實例中,然後通過調用 properties.get(“property”) 在 Bean 中使用它。
  3. @Value — 可以使用 @Value(${‘property’}) 註解在 Bean 中注入特定的屬性。
  4. @ConfigurationProperties — 可以使用 @ConfigurationProperties 加載 Bean 中的層次化屬性。

3. 從外部文件重新加載屬性

為了在運行時更改文件中的屬性,我們應該將該文件放置在 JAR 外。然後,我們使用命令行參數 <em –spring.config.location=file://{路徑到文件} 告知 Spring 它的位置。 另一種方法是將它放在 <em>application.properties</em> 中。

在基於文件進行的屬性中,我們必須選擇一種重新加載文件的方式。 例如,我們可以開發一個端點或調度器來讀取文件並更新屬性。

Apache 的 <em>commons-configuration</em> 是一個方便的庫,用於重新加載文件。 我們可以使用 <em>PropertiesConfiguration</em>,並結合不同的 <em>ReloadingStrategy</em>。

讓我們將 <em>commons-configuration</em> 添加到我們的 <em>pom.xml</em> 中:

<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 作為默認的刷新策略,並設置了默認刷新延遲。這意味着 PropertiesConfiguration 會檢查文件修改日期,如果其上次檢查時間距現在5000毫秒前

我們可以使用 FileChangedReloadingStrategy#setRefreshDelay 自定義延遲。

3.1. 重新加載 環境 屬性

如果想要通過 Environment 實例加載的屬性進行重新加載,則需要 擴展 PropertySource,然後使用 PropertiesConfiguration 從外部屬性文件中返回新的值

讓我們從擴展 PropertySource 開始:

public class ReloadablePropertySource extends PropertySource {

    PropertiesConfiguration propertiesConfiguration;

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

    public ReloadablePropertySource(String name, String path) {
        super(StringUtils.hasText(name) ? path : name);
        try {
            this.propertiesConfiguration = new PropertiesConfiguration(path);
            this.propertiesConfiguration.setReloadingStrategy(new FileChangedReloadingStrategy());
        } catch (Exception e) {
            throw new PropertiesException(e);
        }
    }

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

我們已覆蓋了 getProperty 方法,將其委託給 PropertiesConfiguration#getProperty。因此,它將根據我們的刷新延遲間隔檢查更新後的值。

現在我們將向 Environment 的屬性源中添加我們的 ReloadablePropertySource

@Configuration
public class ReloadablePropertySourceConfig {

    private 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;
    }
}

我們將新的屬性源作為第一項,是因為我們希望它覆蓋任何具有相同鍵的現有屬性。

讓我們創建一個 Bean 來從 Environment 讀取屬性:

@Component
public class EnvironmentConfigBean {

    private Environment environment;

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

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

如果我們需要添加其他可重新加載的外部屬性源,首先必須實現我們的自定義PropertySourceFactory

public class ReloadablePropertySourceFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String s, EncodedResource encodedResource)
      throws IOException {
        Resource internal = encodedResource.getResource();
        if (internal instanceof FileSystemResource)
            return new ReloadablePropertySource(s, ((FileSystemResource) internal)
              .getPath());
        if (internal instanceof FileUrlResource)
            return new ReloadablePropertySource(s, ((FileUrlResource) internal)
              .getURL()
              .getPath());
        return super.createPropertySource(s, encodedResource);
    }
}

然後,我們可以使用 @PropertySource 標記組件的類:

@PropertySource(value = "file:path-to-config", factory = ReloadablePropertySourceFactory.class)

3.2. 重新加載屬性實例

EnvironmentProperties 更適合,尤其當我們需要從文件重新加載屬性時。但是,如果需要,我們可以擴展 java.util.Properties

public class ReloadableProperties extends Properties {
    private 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;
    }
    
    // other overrides
}

我們已覆蓋了 getProperty 方法及其所有重載,然後將其委託給一個 PropertiesConfiguration 實例。現在我們可以創建該類的 Bean,並將其注入到我們的組件中。

3.3. 使用 <em @ConfigurationProperties@ 重新加載 Bean

為了達到與 <em @ConfigurationProperties@ 相同的效果,我們需要重建該實例。但是 Spring 只會為具有 <em prototype 或 <em request 作用域的組件創建新的實例。

因此,我們用於重新加載環境的技術同樣適用於這些組件,但對於單例,我們只能實現一個端點來銷燬和重建 Bean,或者在 Bean 本身處理屬性重新加載。

3.4. 使用 @Value 重新加載 Bean

@Value 註解具有與 @ConfigurationProperties 相同的限制。

4. 通過 Actuator 和雲端刷新屬性

Spring Actuator 提供健康檢查、指標和配置的不同端點,但沒有提供刷新 Bean 的功能。因此,我們需要 Spring Cloud 添加一個 /refresh 端點到 Actuator 中。該端點會刷新 Environment 的所有屬性源,然後發佈一個 EnvironmentChangeEvent

Spring Cloud 還引入了 @RefreshScope,我們可以將其用於配置類或 Bean。因此,默認的 scope 將會是 refresh 而不是 singleton

使用 refresh scope,Spring 會在 EnvironmentChangeEvent 發生時清除這些組件的內部緩存。然後,在 Bean 下一次訪問時,會創建一個新的實例。

讓我們從添加 spring-boot-starter-actuator 到我們的 pom.xml 開始:

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

然後我們將導入 spring-cloud-dependencies

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

<properties>
    <spring-cloud.version>2023.0.1/spring-cloud.version>
</properties>

接下來,我們將添加 spring-cloud-starter

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

最後,我們將啓用刷新端點:

management.endpoints.web.exposure.include=refresh

當我們使用 Spring Cloud 時,可以使用 Config Server 來管理屬性,但我們也可以繼續使用外部文件。現在,我們可以處理兩種其他讀取屬性的方法:<em @Value</em><em @ConfigurationProperties</em>

4.1. 使用 <em @ConfigurationProperties@ 刷新 Bean

讓我們演示如何使用 <em @ConfigurationProperties@ 與 <em @RefreshScope@ 結合使用:

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

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

    //getter and other stuffs
}

我們的 Bean 正在讀取根屬性 “color” 以及 “application.theme”.application.theme.color” 的值後,我們可以調用 /refresh 以在下次訪問時從 Bean 中獲取新值。

4.2. 使用 @Value 刷新 Bean

讓我們創建我們的示例組件:

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

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

刷新過程與上述相同。

但是,需要注意的是,對於具有明確 singleton 作用域的 Bean,使用 /refresh 將不起作用。

5. 結論

在本文中,我們學習瞭如何使用帶有或不帶 Spring Cloud 功能的情況下重新加載屬性。我們還闡述了每種技術的潛在問題和例外情況。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.