Перейти к основному содержимому

Перезагрузка файлов свойств в Spring

· 7 мин. чтения

1. Обзор

В этом руководстве мы собираемся показать, как перезагрузить свойства в приложении Spring .

2. Чтение свойств весной

У нас есть разные варианты доступа к свойствам в Spring:

  1. Environment — мы можем внедрить Environment , а затем использовать Environment#getProperty для чтения данного свойства. Environment содержит различные источники свойств, такие как системные свойства, параметры -D и application.properties (.yml) . Кроме того, в среду можно добавить дополнительные источники свойств с помощью @PropertySource .
  2. Свойства — мы можем загрузить файлы свойств в экземпляр свойств , а затем использовать их в bean-компоненте, вызвав properties.get("property").
  3. @Value — мы можем внедрить определенное свойство в bean-компонент с помощью аннотации @Value(${'property'}) .
  4. @ConfigurationProperties — мы можем использовать @ConfigurationProperties для загрузки иерархических свойств в bean-компоненте.

3. Перезагрузка свойств из внешнего файла

Чтобы изменить свойства файла во время выполнения, мы должны поместить этот файл где-нибудь за пределами jar. Затем мы сообщим Spring, где он находится, с помощью `параметра командной строки –spring.config.location=file://{путь к файлу} . Или мы можем поместить его в application.properties.`

В свойствах файла нам нужно будет выбрать способ перезагрузки файла. Например, мы можем разработать конечную точку или планировщик для чтения файла и обновления свойств.

Одной из удобных библиотек для перезагрузки файла является Apache commons-configuration . Мы можем использовать PropertiesConfiguration с разными ReloadingStrategy .

Давайте добавим конфигурацию commons в наш pom.xml :

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

Затем мы добавляем метод для создания bean-компонента PropertiesConfiguration , который мы будем использовать позже:

@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. Перезагрузка свойств среды

Если мы хотим перезагрузить свойства, загруженные через экземпляр среды , мы должны расширить 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. Следовательно, он будет проверять обновленные значения с интервалами в соответствии с нашей задержкой обновления.

Теперь мы собираемся добавить наш 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;
}
}

Мы добавили новый источник свойства в качестве первого элемента, потому что хотим, чтобы он переопределял любое существующее свойство с тем же ключом.

Давайте создадим 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. Перезагрузка экземпляра свойств

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
}

Мы переопределили getProperty и его перегрузки, а затем делегировали их экземпляру PropertiesConfiguration . Теперь мы можем создать bean-компонент этого класса и внедрить его в наши компоненты.

3.3. Перезагрузка компонента с помощью @ConfigurationProperties

Чтобы получить тот же эффект с @ConfigurationProperties , нам нужно восстановить экземпляр.

Но Spring создаст только новый экземпляр компонентов с областью действия прототипа или запроса .

Таким образом, наш метод перезагрузки среды также будет работать для них, но для синглтонов у нас нет другого выбора, кроме реализации конечной точки для уничтожения и воссоздания компонента или обработки перезагрузки свойства внутри самого компонента.

3.4. Перезагрузка компонента с помощью @Value

Аннотация @Value имеет те же ограничения, что и @ConfigurationProperties .

4. Перезагрузка свойств с помощью Actuator и Cloud

Spring Actuator предоставляет разные конечные точки для работоспособности, метрик и конфигураций, но ничего для обновления bean-компонентов. Таким образом, нам нужно, чтобы Spring Cloud добавил к нему конечную точку /refresh . Эта конечная точка перезагружает все источники свойств среды , а затем публикует событие EnvironmentChangeEvent .

Spring Cloud также представил @RefreshScope , и мы можем использовать его для классов конфигурации или bean-компонентов. В результате область действия по умолчанию будет Refresh вместо singleton .

Используя область обновления , Spring очистит свой внутренний кеш от этих компонентов в EnvironmentChangeEvent . Затем при следующем доступе к компоненту создается новый экземпляр. ``

Начнем с добавления 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>Greenwich.SR1</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, мы можем настроить сервер конфигурации для управления свойствами, но мы также можем продолжить работу с нашими внешними файлами. Теперь мы можем обрабатывать два других метода чтения свойств: @Value и @ConfigurationProperties .

4.1. Обновить компоненты с помощью @ConfigurationProperties

Давайте покажем, как использовать @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
}

Наш bean-компонент считывает свойство « color» из корня «application . свойство «тема» . Обратите внимание, что нам нужен метод setter в соответствии с документацией Spring.

После того, как мы изменим значение « application.theme.color » в нашем внешнем конфигурационном файле, мы можем вызвать /refresh , чтобы получить новое значение из bean-компонента при следующем доступе.

4.2. Обновить бины с помощью @Value

Давайте создадим наш образец компонента:

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

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

Процесс обновления аналогичен описанному выше.

Однако необходимо отметить, что /refresh не будет работать для bean-компонентов с явно заданной областью действия singleton .

5. Вывод

В этом руководстве мы продемонстрировали, как перезагружать свойства с функциями Spring Cloud или без них. Кроме того, мы показали подводные камни и исключения каждого из методов.

Полный код доступен в нашем проекте GitHub .