SpringBoot動態(tài)修改配置的十種方法
引言
在SpringBoot應(yīng)用中,配置信息通常通過application.properties或application.yml文件靜態(tài)定義,應(yīng)用啟動后這些配置就固定下來了。
但我們常常需要在不重啟應(yīng)用的情況下動態(tài)修改配置,以實(shí)現(xiàn)灰度發(fā)布、A/B測試、動態(tài)調(diào)整線程池參數(shù)、切換功能開關(guān)等場景。
本文將介紹SpringBoot中10種實(shí)現(xiàn)配置動態(tài)修改的方法。
1. @RefreshScope結(jié)合Actuator刷新端點(diǎn)
Spring Cloud提供的@RefreshScope注解是實(shí)現(xiàn)配置熱刷新的基礎(chǔ)方法。
實(shí)現(xiàn)步驟
- 添加依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
- 開啟刷新端點(diǎn):
management.endpoints.web.exposure.include=refresh
- 給配置類添加
@RefreshScope注解:
@RefreshScope
@RestController
public class ConfigController {
@Value("${app.message:Default message}")
private String message;
@GetMapping("/message")
public String getMessage() {
return message;
}
}
- 修改配置后,調(diào)用刷新端點(diǎn):
curl -X POST http://localhost:8080/actuator/refresh
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 實(shí)現(xiàn)簡單,利用Spring Cloud提供的現(xiàn)成功能
- 無需引入額外的配置中心
缺點(diǎn)
- 需要手動觸發(fā)刷新
- 只能刷新單個實(shí)例,在集群環(huán)境中需要逐個調(diào)用
- 只能重新加載配置源中的值,無法動態(tài)添加新配置
2. Spring Cloud Config配置中心
Spring Cloud Config提供了一個中心化的配置服務(wù)器,支持配置文件的版本控制和動態(tài)刷新。
實(shí)現(xiàn)步驟
- 設(shè)置Config Server:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
spring.cloud.config.server.git.uri=https://github.com/your-repo/config
- 客戶端配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
# bootstrap.properties spring.application.name=my-service spring.cloud.config.uri=http://localhost:8888
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 提供配置的版本控制
- 支持配置的環(huán)境隔離
- 通過Spring Cloud Bus可實(shí)現(xiàn)集群配置的自動刷新
缺點(diǎn)
- 引入了額外的基礎(chǔ)設(shè)施復(fù)雜性
- 依賴額外的消息總線實(shí)現(xiàn)集群刷新
- 配置更新有一定延遲
3. 基于數(shù)據(jù)庫的配置存儲
將配置信息存儲在數(shù)據(jù)庫中,通過定時任務(wù)或事件觸發(fā)機(jī)制實(shí)現(xiàn)配置刷新。
實(shí)現(xiàn)方案
- 創(chuàng)建配置表:
CREATE TABLE app_config (
config_key VARCHAR(100) PRIMARY KEY,
config_value VARCHAR(500) NOT NULL,
description VARCHAR(200),
update_time TIMESTAMP
);
- 實(shí)現(xiàn)配置加載和刷新:
@Service
public class DatabaseConfigService {
@Autowired
private JdbcTemplate jdbcTemplate;
private Map<String, String> configCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
loadAllConfig();
}
@Scheduled(fixedDelay = 60000) // 每分鐘刷新
public void loadAllConfig() {
List<Map<String, Object>> rows = jdbcTemplate.queryForList("SELECT config_key, config_value FROM app_config");
for (Map<String, Object> row : rows) {
configCache.put((String) row.get("config_key"), (String) row.get("config_value"));
}
}
public String getConfig(String key, String defaultValue) {
return configCache.getOrDefault(key, defaultValue);
}
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 簡單直接,無需額外組件
- 可以通過管理界面實(shí)現(xiàn)配置可視化管理
- 配置持久化,重啟不丟失
缺點(diǎn)
- 刷新延遲取決于定時任務(wù)間隔
- 數(shù)據(jù)庫成為潛在的單點(diǎn)故障
- 需要自行實(shí)現(xiàn)配置的版本控制和權(quán)限管理
4. 使用ZooKeeper管理配置
利用ZooKeeper的數(shù)據(jù)變更通知機(jī)制,實(shí)現(xiàn)配置的實(shí)時動態(tài)更新。
實(shí)現(xiàn)步驟
- 添加依賴:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
- 實(shí)現(xiàn)配置監(jiān)聽:
@Component
public class ZookeeperConfigManager {
private final CuratorFramework client;
private final Map<String, String> configCache = new ConcurrentHashMap<>();
@Autowired
public ZookeeperConfigManager(CuratorFramework client) {
this.client = client;
initConfig();
}
private void initConfig() {
try {
String configPath = "/config";
if (client.checkExists().forPath(configPath) == null) {
client.create().creatingParentsIfNeeded().forPath(configPath);
}
List<String> keys = client.getChildren().forPath(configPath);
for (String key : keys) {
String fullPath = configPath + "/" + key;
byte[] data = client.getData().forPath(fullPath);
configCache.put(key, new String(data));
// 添加監(jiān)聽器
NodeCache nodeCache = new NodeCache(client, fullPath);
nodeCache.getListenable().addListener(() -> {
byte[] newData = nodeCache.getCurrentData().getData();
configCache.put(key, new String(newData));
System.out.println("Config updated: " + key + " = " + new String(newData));
});
nodeCache.start();
}
} catch (Exception e) {
throw new RuntimeException("Failed to initialize config from ZooKeeper", e);
}
}
public String getConfig(String key, String defaultValue) {
return configCache.getOrDefault(key, defaultValue);
}
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 實(shí)時通知,配置變更后立即生效
- ZooKeeper提供高可用性保證
- 適合分布式環(huán)境下的配置同步
缺點(diǎn)
- 需要維護(hù)ZooKeeper集群
- 配置管理不如專用配置中心直觀
- 存儲大量配置時性能可能受影響
5. Redis發(fā)布訂閱機(jī)制實(shí)現(xiàn)配置更新
利用Redis的發(fā)布訂閱功能,實(shí)現(xiàn)配置變更的實(shí)時通知。
實(shí)現(xiàn)方案
- 添加依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 實(shí)現(xiàn)配置刷新監(jiān)聽:
@Component
public class RedisConfigManager {
@Autowired
private StringRedisTemplate redisTemplate;
private final Map<String, String> configCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
loadAllConfig();
subscribeConfigChanges();
}
private void loadAllConfig() {
Set<String> keys = redisTemplate.keys("config:*");
if (keys != null) {
for (String key : keys) {
String value = redisTemplate.opsForValue().get(key);
configCache.put(key.replace("config:", ""), value);
}
}
}
private void subscribeConfigChanges() {
redisTemplate.getConnectionFactory().getConnection().subscribe(
(message, pattern) -> {
String[] parts = new String(message.getBody()).split("=");
if (parts.length == 2) {
configCache.put(parts[0], parts[1]);
}
},
"config-channel".getBytes()
);
}
public String getConfig(String key, String defaultValue) {
return configCache.getOrDefault(key, defaultValue);
}
// 更新配置的方法(管理端使用)
public void updateConfig(String key, String value) {
redisTemplate.opsForValue().set("config:" + key, value);
redisTemplate.convertAndSend("config-channel", key + "=" + value);
}
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 實(shí)現(xiàn)簡單,利用Redis的發(fā)布訂閱機(jī)制
- 集群環(huán)境下配置同步實(shí)時高效
- 可以與現(xiàn)有Redis基礎(chǔ)設(shè)施集成
缺點(diǎn)
- 依賴Redis的可用性
- 需要確保消息不丟失
- 缺乏版本控制和審計(jì)功能
6. 自定義配置加載器和監(jiān)聽器
通過自定義Spring的PropertySource和文件監(jiān)聽機(jī)制,實(shí)現(xiàn)本地配置文件的動態(tài)加載。
實(shí)現(xiàn)方案
@Component
public class DynamicPropertySource implements ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicPropertySource.class);
private ConfigurableApplicationContext applicationContext;
private File configFile;
private Properties properties = new Properties();
private FileWatcher fileWatcher;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
try {
configFile = new File("config/dynamic.properties");
if (configFile.exists()) {
loadProperties();
registerPropertySource();
startFileWatcher();
}
} catch (Exception e) {
logger.error("Failed to initialize dynamic property source", e);
}
}
private void loadProperties() throws IOException {
try (FileInputStream fis = new FileInputStream(configFile)) {
properties.load(fis);
}
}
private void registerPropertySource() {
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
PropertiesPropertySource propertySource = new PropertiesPropertySource("dynamic", properties);
propertySources.addFirst(propertySource);
}
private void startFileWatcher() {
fileWatcher = new FileWatcher(configFile);
fileWatcher.setListener(new FileChangeListener() {
@Override
public void fileChanged() {
try {
Properties newProps = new Properties();
try (FileInputStream fis = new FileInputStream(configFile)) {
newProps.load(fis);
}
// 更新已有屬性
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
PropertiesPropertySource oldSource = (PropertiesPropertySource) propertySources.get("dynamic");
if (oldSource != null) {
propertySources.replace("dynamic", new PropertiesPropertySource("dynamic", newProps));
}
// 發(fā)布配置變更事件
applicationContext.publishEvent(new EnvironmentChangeEvent(Collections.singleton("dynamic")));
logger.info("Dynamic properties reloaded");
} catch (Exception e) {
logger.error("Failed to reload properties", e);
}
}
});
fileWatcher.start();
}
// 文件監(jiān)聽器實(shí)現(xiàn)(簡化版)
private static class FileWatcher extends Thread {
private final File file;
private FileChangeListener listener;
private long lastModified;
public FileWatcher(File file) {
this.file = file;
this.lastModified = file.lastModified();
}
public void setListener(FileChangeListener listener) {
this.listener = listener;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
long newLastModified = file.lastModified();
if (newLastModified != lastModified) {
lastModified = newLastModified;
if (listener != null) {
listener.fileChanged();
}
}
Thread.sleep(5000); // 檢查間隔
}
} catch (InterruptedException e) {
// 線程中斷
}
}
}
private interface FileChangeListener {
void fileChanged();
}
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 不依賴外部服務(wù),完全自主控制
- 可以監(jiān)控本地文件變更實(shí)現(xiàn)實(shí)時刷新
- 適合單體應(yīng)用或簡單場景
缺點(diǎn)
- 配置分發(fā)需要額外機(jī)制支持
- 集群環(huán)境下配置一致性難以保證
- 需要較多自定義代碼
7. Apollo配置中心
攜程開源的Apollo是一個功能強(qiáng)大的分布式配置中心,提供配置修改、發(fā)布、回滾等完整功能。
實(shí)現(xiàn)步驟
- 添加依賴:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>2.0.1</version>
</dependency>
- 配置Apollo客戶端:
# app.properties app.id=your-app-id apollo.meta=http://apollo-config-service:8080
- 啟用Apollo:
@SpringBootApplication
@EnableApolloConfig
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 使用配置:
@Component
public class SampleService {
@Value("${timeout:1000}")
private int timeout;
// 監(jiān)聽特定配置變更
@ApolloConfigChangeListener
public void onConfigChange(ConfigChangeEvent event) {
if (event.isChanged("timeout")) {
ConfigChange change = event.getChange("timeout");
System.out.println("timeout changed from " + change.getOldValue() + " to " + change.getNewValue());
// 可以在這里執(zhí)行特定邏輯,如重新初始化線程池等
}
}
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 提供完整的配置管理界面
- 支持配置的灰度發(fā)布
- 具備權(quán)限控制和操作審計(jì)
- 集群自動同步,無需手動刷新
缺點(diǎn)
- 需要部署和維護(hù)Apollo基礎(chǔ)設(shè)施
- 學(xué)習(xí)成本相對較高
- 小型項(xiàng)目可能過于重量級
8. Nacos配置管理
阿里開源的Nacos既是服務(wù)發(fā)現(xiàn)組件,也是配置中心,廣泛應(yīng)用于Spring Cloud Alibaba生態(tài)。
實(shí)現(xiàn)步驟
- 添加依賴:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 配置Nacos:
# bootstrap.properties spring.application.name=my-service spring.cloud.nacos.config.server-addr=127.0.0.1:8848 # 支持多配置文件 spring.cloud.nacos.config.extension-configs[0].data-id=database.properties spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP spring.cloud.nacos.config.extension-configs[0].refresh=true
- 使用配置:
@RestController
@RefreshScope
public class ConfigController {
@Value("${useLocalCache:false}")
private boolean useLocalCache;
@GetMapping("/cache")
public boolean getUseLocalCache() {
return useLocalCache;
}
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 與Spring Cloud Alibaba生態(tài)無縫集成
- 配置和服務(wù)發(fā)現(xiàn)功能二合一
- 輕量級,易于部署和使用
- 支持配置的動態(tài)刷新和監(jiān)聽
缺點(diǎn)
- 部分高級功能不如Apollo豐富
- 需要額外維護(hù)Nacos服務(wù)器
- 需要使用bootstrap配置機(jī)制
9. Spring Boot Admin與Actuator結(jié)合
Spring Boot Admin提供了Web UI來管理和監(jiān)控Spring Boot應(yīng)用,結(jié)合Actuator的環(huán)境端點(diǎn)可以實(shí)現(xiàn)配置的可視化管理。
實(shí)現(xiàn)步驟
- 設(shè)置Spring Boot Admin服務(wù)器:
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.7.0</version>
</dependency>
@SpringBootApplication
@EnableAdminServer
public class AdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(AdminServerApplication.class, args);
}
}
- 配置客戶端應(yīng)用:
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
spring.boot.admin.client.url=http://localhost:8080 management.endpoints.web.exposure.include=* management.endpoint.env.post.enabled=true
- 通過Spring Boot Admin UI修改配置
Spring Boot Admin提供UI界面,可以查看和修改應(yīng)用的環(huán)境屬性。通過發(fā)送POST請求到/actuator/env端點(diǎn)修改配置。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 提供可視化操作界面
- 與Spring Boot自身監(jiān)控功能集成
- 無需額外的配置中心組件
缺點(diǎn)
- 修改的配置不持久化,應(yīng)用重啟后丟失
- 安全性較弱,需要額外加強(qiáng)保護(hù)
- 不適合大規(guī)模生產(chǎn)環(huán)境的配置管理
10. 使用@ConfigurationProperties結(jié)合EventListener
利用Spring的事件機(jī)制和@ConfigurationProperties綁定功能,實(shí)現(xiàn)配置的動態(tài)更新。
實(shí)現(xiàn)方案
- 定義配置屬性類:
@Component
@ConfigurationProperties(prefix = "app")
@Setter
@Getter
public class ApplicationProperties {
private int connectionTimeout;
private int readTimeout;
private int maxConnections;
private Map<String, String> features = new HashMap<>();
// 初始化客戶端的方法
public HttpClient buildHttpClient() {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(connectionTimeout))
.build();
}
}
- 添加配置刷新機(jī)制:
@Component
@RequiredArgsConstructor
public class ConfigRefresher {
private final ApplicationProperties properties;
private final ApplicationContext applicationContext;
private HttpClient httpClient;
@PostConstruct
public void init() {
refreshHttpClient();
}
@EventListener(EnvironmentChangeEvent.class)
public void onEnvironmentChange() {
refreshHttpClient();
}
private void refreshHttpClient() {
httpClient = properties.buildHttpClient();
System.out.println("HttpClient refreshed with timeout: " + properties.getConnectionTimeout());
}
public HttpClient getHttpClient() {
return this.httpClient;
}
// 手動觸發(fā)配置刷新的方法
public void refreshProperties(Map<String, Object> newProps) {
PropertiesPropertySource propertySource = new PropertiesPropertySource(
"dynamic", convertToProperties(newProps));
ConfigurableEnvironment env = (ConfigurableEnvironment) applicationContext.getEnvironment();
env.getPropertySources().addFirst(propertySource);
// 觸發(fā)環(huán)境變更事件
applicationContext.publishEvent(new EnvironmentChangeEvent(newProps.keySet()));
}
private Properties convertToProperties(Map<String, Object> map) {
Properties properties = new Properties();
for (Map.Entry<String, Object> entry : map.entrySet()) {
properties.put(entry.getKey(), entry.getValue().toString());
}
return properties;
}
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 強(qiáng)類型的配置綁定
- 利用Spring內(nèi)置機(jī)制,無需額外組件
- 靈活性高,可與其他配置源結(jié)合
缺點(diǎn)
- 需要編寫較多代碼
- 配置變更通知需要額外實(shí)現(xiàn)
- 不適合大規(guī)?;蚩绶?wù)的配置管理
方法比較與選擇指南
| 方法 | 易用性 | 功能完整性 | 適用規(guī)模 | 實(shí)時性 |
|---|---|---|---|---|
| @RefreshScope+Actuator | ★★★★★ | ★★ | 小型 | 手動觸發(fā) |
| Spring Cloud Config | ★★★ | ★★★★ | 中大型 | 需配置 |
| 數(shù)據(jù)庫存儲 | ★★★★ | ★★★ | 中型 | 定時刷新 |
| ZooKeeper | ★★★ | ★★★ | 中型 | 實(shí)時 |
| Redis發(fā)布訂閱 | ★★★★ | ★★★ | 中型 | 實(shí)時 |
| 自定義配置加載器 | ★★ | ★★★ | 小型 | 定時刷新 |
| Apollo | ★★★ | ★★★★★ | 中大型 | 實(shí)時 |
| Nacos | ★★★★ | ★★★★ | 中大型 | 實(shí)時 |
| Spring Boot Admin | ★★★★ | ★★ | 小型 | 手動觸發(fā) |
| @ConfigurationProperties+事件 | ★★★ | ★★★ | 小型 | 事件觸發(fā) |
總結(jié)
動態(tài)配置修改能夠提升系統(tǒng)的靈活性和可管理性,選擇合適的動態(tài)配置方案應(yīng)綜合考慮應(yīng)用規(guī)模、團(tuán)隊(duì)熟悉度、基礎(chǔ)設(shè)施現(xiàn)狀和業(yè)務(wù)需求。
無論選擇哪種方案,確保配置的安全性、一致性和可追溯性都是至關(guān)重要的。
以上就是SpringBoot動態(tài)修改配置的十種方法的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot動態(tài)修改配置的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Java語言實(shí)現(xiàn)Socket通信的實(shí)例
今天小編就為大家分享一篇關(guān)于基于Java語言實(shí)現(xiàn)Socket通信的實(shí)例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01
SpringSecurity+OAuth2.0?搭建認(rèn)證中心和資源服務(wù)中心流程分析
OAuth?2.0?主要用于在互聯(lián)網(wǎng)上安全地委托授權(quán),廣泛應(yīng)用于身份驗(yàn)證和授權(quán)場景,這篇文章介紹SpringSecurity+OAuth2.0?搭建認(rèn)證中心和資源服務(wù)中心,感興趣的朋友一起看看吧2024-01-01
小議Java的源文件的聲明規(guī)則以及編程風(fēng)格
這篇文章主要介紹了小議Java的源文件的聲明規(guī)則以及編程風(fēng)格,僅給Java初學(xué)者作一個簡單的示范,需要的朋友可以參考下2015-09-09
Lombok同時使?@Data和@Builder踩坑總結(jié)
這篇文章主要介紹了Lombok同時使?@Data和@Builder踩坑總結(jié),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值需要的小伙伴可以參考一下,希望對你的學(xué)習(xí)有所幫助2022-05-05
將List集合中的map對象轉(zhuǎn)為List<對象>形式實(shí)例代碼
這篇文章主要介紹了將List集合中的map對象轉(zhuǎn)為List<對象>形式實(shí)例代碼,具有一定借鑒價值,需要的朋友可以參考下2018-01-01
如何保證RabbitMQ全鏈路數(shù)據(jù)100%不丟失問題
這篇文章主要介紹了如何保證RabbitMQ全鏈路數(shù)據(jù)100%不丟失問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
springboot項(xiàng)目中mybatis-plus@Mapper注入失敗問題
這篇文章主要介紹了springboot項(xiàng)目中mybatis-plus@Mapper注入失敗問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07

