1. Обзор
В этом руководстве мы собираемся показать, как перезагрузить свойства в приложении Spring .
2. Чтение свойств весной
У нас есть разные варианты доступа к свойствам в Spring:
Environment
— мы можем внедритьEnvironment
, а затем использоватьEnvironment#getProperty
для чтения данного свойства.Environment
содержит различные источники свойств, такие как системные свойства, параметры-D
иapplication.properties (.yml)
.Кроме того, в среду
можно добавить дополнительные источники свойств с помощью@PropertySource
.Свойства
— мы можем загрузить файлы свойств в экземплярсвойств , а затем использовать их в bean-компоненте, вызвав
properties.get("property").
@Value
— мы можем внедрить определенное свойство в bean-компонент с помощьюаннотации @Value(${'property'})
.@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 .