Reloading Properties Files in Spring – 在Spring中重新加载属性文件

最后修改: 2019年 7月 30日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll learn how to reload properties in a Spring application.

在本教程中,我们将学习如何在Spring应用程序中重新加载属性

2. Reading Properties in Spring

2.在Spring阅读属性

We have several different options to access properties in Spring:

我们有几种不同的选择来访问Spring的财产。

  1. Environment — We can inject Environment and then use Environment#getProperty to read a given property. Environment contains different property sources, like system properties, -D parameters, and application.properties (.yml). Extra property sources can also be added to the Environment using @PropertySource.
  2. Properties — We can load properties files into a Properties instance, and then use it in a bean by calling properties.get(“property”).
  3. @Value — We can inject a specific property in a bean with the @Value(${‘property’}) annotation.
  4. @ConfigurationProperties — We can use @ConfigurationProperties to load hierarchical properties in a bean.

3. Reloading Properties From External File

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

To change properties in a file during runtime, we should place that file somewhere outside the jar. Then we tell Spring where it is with the command-line parameter –spring.config.location=file://{path to file}. Alternatively, we can put it in application.properties.

为了在运行期间改变文件中的属性,我们应该将该文件放在jar之外的某个地方。然后我们用命令行参数-spring.config.location=file://{path to file}告诉Spring它在哪里。或者,我们也可以把它放在application.properties.中。

In file-based properties, we have to choose a way to reload the file. For example, we can develop an endpoint or scheduler to read the file and update the properties.

在基于文件的属性中,我们必须选择一种方式来重新加载文件。例如,我们可以开发一个端点或调度器来读取文件并更新属性。

One handy library to reload the file is Apache’s commons-configuration. We can use PropertiesConfiguration with different ReloadingStrategy.

一个方便的重新加载文件的库是Apache的commons-configuration。我们可以使用PropertiesConfiguration与不同的 ReloadingStrategy

Let’s add commons-configuration to our pom.xml:

让我们把commons-configuration添加到我们的pom.xml

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

Then we’ll add a method to create a PropertiesConfiguration bean, which we’ll use later:

然后我们将添加一个方法来创建一个PropertiesConfigurationbean,我们将在后面使用它。

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

In the above code, we set FileChangedReloadingStrategy as the reloading strategy with a default refresh delay. This means that PropertiesConfiguration checks for the file modification date if its last check was before 5000ms ago.

在上面的代码中,我们将FileChangedReloadingStrategy设置为具有默认刷新延迟的重新加载策略。这意味着PropertiesConfiguration会检查文件修改日期,如果其最后一次检查是在5000ms前

We can customize the delay using FileChangedReloadingStrategy#setRefreshDelay.

我们可以使用FileChangedReloadingStrategy#setRefreshDelay.自定义延迟。

3.1. Reloading Environment Properties

3.1.重新加载环境属性

If we want to reload the properties loaded through an Environment instance, we have to extend the PropertySource, and then use PropertiesConfiguration to return new values from the external property file.

如果我们想重新加载通过环境实例加载的属性,我们必须扩展PropertySource,,然后使用PropertiesConfiguration从外部属性文件返回新值

Let’s start with extending the PropertySource:

让我们从扩展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);
    }
}

We’ve overridden the getProperty method to delegate it to PropertiesConfiguration#getProperty. Therefore, it’ll check for updated values in intervals according to our refresh delay.

我们重写了getProperty方法,将其委托给PropertiesConfiguration#getProperty。因此,它将根据我们的刷新延迟,以一定的间隔检查更新的值。

Now we’ll add our ReloadablePropertySource to Environment‘s property sources:

现在我们将把我们的ReloadablePropertySource添加到Environment的属性源中。

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

We added the new property source as the first item because we want it to override any existing property with the same key.

我们将新的属性源作为第一项添加,因为我们希望它能够覆盖任何具有相同键的现有属性。

Let’s create a bean to read a property from Environment:

让我们创建一个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");
    }
}

If we need to add other reloadable external properties sources, we first have to implement our custom PropertySourceFactory:

如果我们需要添加其他可重新加载的外部属性源,我们首先要实现我们的自定义 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);
    }
}

Then we can annotate the class of a component with @PropertySource:

然后我们可以用@PropertySource来注解一个组件的类。

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

3.2. Reloading Properties Instance

3.2.重新加载属性实例

Environment is a better choice than Properties, especially when we need to reload properties from a file. However, if we need it, we can extend the java.util.Properties:

Environment是比Properties更好的选择,特别是当我们需要从文件中重新加载属性时。然而,如果我们需要,我们可以扩展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
}

We’ve overridden getProperty and its overloads, then delegated it to a PropertiesConfiguration instance. Now we can create a bean of this class, and inject it in our components.

我们重载了getProperty及其重载,然后将其委托给一个PropertiesConfiguration实例。现在我们可以创建一个这个类的bean,并将其注入我们的组件中。

3.3. Reloading Bean With @ConfigurationProperties

3.3.用@ConfigurationProperties重新加载 Bean

To get the same effect with @ConfigurationProperties, we’d need to reconstruct the instance. But Spring will only create a new instance of components with the prototype or request scope.

为了用@ConfigurationProperties获得同样的效果,我们需要重构实例。但Spring只会为prototyperequest范围的组件创建一个新的实例。

Consequently, our technique to reload the environment will also work for them, but for singletons, we have no choice but to implement an endpoint to destroy and recreate the bean, or to handle the property reload inside the bean itself.

因此,我们重新加载环境的技术也会对它们起作用,但对于单子,我们别无选择,只能实现一个端点来销毁和重新创建Bean,或者在Bean本身中处理属性的重新加载。

3.4. Reloading Bean With @Value

3.4.用@Value重新加载 Bean

The @Value annotation presents the same limitations as @ConfigurationProperties.

@Value注解提出了与@ConfigurationProperties相同的限制。

4. Reloading Properties by Actuator and Cloud

4.按执行器和云计算的重装属性

Spring Actuator provides different endpoints for health, metrics, and configs, but nothing for refreshing beans. Thus, we need Spring Cloud to add a /refresh endpoint to it. This endpoint reloads all property sources of Environment, and then publishes an EnvironmentChangeEvent.

Spring Actuator为健康、指标和配置提供了不同的端点,但没有为刷新Bean提供任何服务。因此,我们需要Spring Cloud为其添加一个/refresh端点。该端点将重新加载Environment的所有属性源,然后发布EnvironmentChangeEvent.

Spring Cloud has also introduced @RefreshScope, and we can use it for configuration classes or beans. As a result, the default scope will be refresh instead of singleton.

Spring Cloud还引入了@RefreshScope,我们可以将其用于配置类或bean。因此,默认范围将是refresh,而不是singleton

Using the refresh scope, Spring will clear its internal cache of these components on an EnvironmentChangeEvent. Then, on the next access to the bean, a new instance is created.

使用refresh 范围,Spring将在EnvironmentChangeEvent上清除其内部缓存的这些组件。然后,在下次访问Bean时,将创建一个新的实例。

Let’s start by adding spring-boot-starter-actuator to our pom.xml:

让我们先把spring-boot-starter-actuator加入我们的pom.xml

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

Then we’ll import spring-cloud-dependencies:

然后我们将导入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>Greenwich.SR1</spring-cloud.version>
</properties>

Next, we’ll add spring-cloud-starter:

接下来,我们将添加spring-cloud-starter

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

Finally, we’ll enable the refresh endpoint:

最后,我们将启用刷新端点。

management.endpoints.web.exposure.include=refresh

When we use Spring Cloud, we can set up a Config Server to manage the properties, but we can also continue with our external files. Now we can handle two other methods of reading properties: @Value and @ConfigurationProperties.

当我们使用Spring Cloud时,我们可以设置一个Config Server来管理属性,但我们也可以继续使用我们的外部文件。现在我们可以处理另外两种读取属性的方法。@Value@ConfigurationProperties

4.1. Refresh Beans With @ConfigurationProperties

4.1.用@ConfigurationProperties刷新Bean

Let’s demonstrate how to use @ConfigurationProperties with @RefreshScope:

让我们演示一下如何使用@ConfigurationProperties@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
}

Our bean is reading the “color” property from the root “application.theme” propertyNote that we do need the setter method, per Spring’s documentation.

我们的Bean正在从根“application.theme” property读取”color”属性。 注意,根据Spring的文档,我们确实需要setter方法。

After we change the value of “application.theme.color” in our external config file, we can call /refresh so that we can get the new value from the bean on our next access.

在我们改变了外部配置文件中”application.theme.color“的值后,我们可以调用/refresh,这样我们就可以在下次访问时从Bean中获得新的值。

4.2. Refresh Beans With @Value

4.2.用@Value刷新Bean

Let’s create our sample component:

让我们来创建我们的示例组件。

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

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

The process of refreshing is the same as above.

刷新的过程与上述相同。

However, it’s necessary to note that /refresh won’t work for beans with an explicit singleton scope.

然而,需要注意的是,/refresh不会对具有明确singleton范围的Bean起作用。

5. Conclusion

5.总结

In this article, we learned how to reload properties with or without Spring Cloud features. We also illustrated the pitfalls and exceptions of each of the techniques.

在这篇文章中,我们学习了如何在有或没有Spring Cloud功能的情况下重新加载属性。我们还说明了每种技术的陷阱和例外情况。

The complete code is available in our GitHub project.

完整的代码可在我们的GitHub项目中获得