Вопрос или проблема
У меня есть проект, в котором я хочу обновлять свойства из application.properties во время выполнения и пересоздавать бины, которые зависят от конфигурации, и сам конфигурационный бин с новым/обновленным значением. Бины уничтожаются, когда я вызываю http://localhost:8888/actuator/refresh, но значение не обновляется. Шаги, которые я выполняю:
- Я вызываю http://localhost:8888/api/message, чтобы проверить текущее значение свойства.
- Я редактирую файл application.properties.
- Я вызываю http://localhost:8888/actuator/refresh.
- Я снова вызываю /api/message, но значение не обновляется. И в логах, когда вызывается @PreDestroy, я также вижу старое значение.
Я также пробовал использовать аннотацию @Value, но безуспешно и не могу понять, в чем проблема.
Буду признателен за любую помощь 🙂
Ниже приведен код:
ConfigProperties.java
package org.example.refreshscopeworkingproject;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "demo")
@RefreshScope
public class ConfigProperties {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@PostConstruct
public void init() {
System.out.println("ConfigProperties bean created with message: " + message);
}
@PreDestroy
public void destroy() {
System.out.println("ConfigProperties destroyed");
}
}
DemoController.java
package org.example.refreshscopeworkingproject;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
@RefreshScope
public class DemoController {
private final ConfigProperties configProperties;
public DemoController(ConfigProperties configProperties) {
this.configProperties = configProperties;
}
@GetMapping("/message")
public String getMessage() {
return configProperties.getMessage();
}
@PostConstruct
public void init() {
System.out.println("Controller initialized");
}
@PreDestroy
public void destroy() {
System.out.println("Controller destroyed");
}
}
RefreshScopeWorkingProjectApplication.java
package org.example.refreshscopeworkingproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RefreshScopeWorkingProjectApplication {
public static void main(String[] args) {
SpringApplication.run(RefreshScopeWorkingProjectApplication.class, args);
}
}
application.properties :
demo.message=Hello, RefreshScope1111!
management.endpoints.web.exposure.include=*
logging.level.org.springframework.boot.actuate=DEBUG
logging.level.org.springframework.boot.context.properties=DEBUG
server.port=8888
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>RefreshScopeWorkingProject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>RefreshScopeWorkingProject</name>
<description>RefreshScopeWorkingProject</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Ответ или решение
Ваша проблема касается обновления значений свойств из файла application.properties
в приложении Spring Boot с использованием аннотации @RefreshScope
. На первый взгляд, у вас уже настроено все правильно: классы помечены нужными аннотациями для повторного создания бинов, и вызов эндпоинта /actuator/refresh
тоже осуществляется. Однако, давайте разберёмся, почему само значение свойства остается старым.
Возможные причины проблемы:
-
Отсутствие поддержки изменения файлов на лету:
Spring Boot по умолчанию не поддерживает автоматическую загрузку измененных локальных файлов свойств. Больше всего обновление свойств эффективно при использовании Spring Cloud Config Server, который внешне управляет конфигурациями и перезагружает их. -
Кэширование значений:
Даже если бин с аннотацией@RefreshScope
пересоздаётся, значение свойства может кешироваться на уровне другого элемента вашей системы. -
Ошибки при конфигурации
@ConfigurationProperties
:
Убедитесь, что все свойства хорошо связаны с префиксом в вашем классе конфигурации. Из-за этого может возникнуть несоответствие при обновлении значений.
Рекомендации по исправлению:
-
Используйте Spring Cloud Config Server:
Для полноценного обновления конфигурации через Spring Cloud, стоит рассмотреть возможность использования Spring Cloud Config Server. Он позволяет хранить конфигурационные свойства в репозитории (например, Git), и ваш клиент может получить обновления напрямую от Конфигурационного Сервера. -
Убедитесь в корректности префиксов и аннотаций:
Проверьте, что вашConfigProperties
класс правильно структурирован, и все префиксы совпадают с вашимapplication.properties
. -
Посмотрите на использование других PropertySource:
Возможно, у вас есть другие источники конфигурации, которые переопределяют местные свойства. Убедитесь, что при загрузке конфигурации не присутствует Парсон настроек, влияющих на это поведение. -
Логирование и мониторинг:
Активируйте вывод дебаг-логов дляorg.springframework.boot.context.properties
, чтобы проверить успешность загрузки обновленных свойств. Это поможет определить, загружаются ли ваши новые значения.
Заключение
Чтобы отладка была более успешной, начните с простого использования @RefreshScope
, и далее переходите к более сложным решениям, таким как Spring Cloud Config Server, если ваш проект нуждается в динамическом управлении конфигурацией в более сложной инфраструктуре. Надеюсь, что эти рекомендации помогут вам настроить обновление свойств в вашем приложении.