需求
系統遇到這樣一個需求,線上環境在配置文件發生變動時,可以不用經過重啟,通過刷新接口的方式得到配置文件的加載,主要目的是為了迅速部署,避免因手動重啟,出現數據或任務丟失的問題
問題
1.程序中如何獲取修改后的配置
2.某些配置值是應用在bean里面的,在程序初始化的時候已經注入,如何修改這一部分。
解決
第一步,添加適合自己springboot版本的Springcloud context依賴,若Springboot版本低,maven可能會引不上高版本的context
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> </dependency> </dependencies>
我們這里參考 Spring-Cloud-Config 主要借助 org.springframework.cloud.context.refresh.ContextRefresher 這個類來實現配置刷新,因此需要對 SpringBoot 項目做一點修改。
代碼演示
配置文件: application.yml
server:
refresh: ${random.long}
key: refresh-test
config:
uuid: ${random.uuid}
讀取配置的Bean,兩種獲取方式分別如下
@Component @ConfigurationProperties(prefix = "server") public class ServerConfig { private String key; private Long refresh; }
開啟刷新@Value的
注解方式,注意下面的@RefreshScoe
注解,這個必須有,否則更新后的配置不會同步
@RefreshScope @Component public class ValueConfig { @Value("${config.uuid}") private String uuid; }
測試 Controller
如下
@RestController public class DemoController { @Autowired private ContextRefresher contextRefresher; @Autowired private ServerConfig serverConfig; @Autowired private ValueConfig valueConfig; @GetMapping(path = "/show") public String show() { JSONObject res = new JSONObject(); res.put("biz", JSONObject.toJSONString(serverConfig)); res.put("uuid", valueConfig.getUuid()); return res.toJSONString(); } @GetMapping(path = "/refresh") public String refresh() { new Thread(() -> contextRefresher.refresh()).start(); return show(); } }
3. 實例演示
啟動上面的應用,然后開啟愉快的測試,調用refresh接口,發現每次的返回都不一樣(因為配置文件使用了random隨機生成),但是訪問show接口時,每次返回的都是一樣的,也就是說refresh接口中確實實現了配置的刷新。
說明
- 使用
ConfigurationProperties
方式獲取注解時,自動支持刷新配置 - 使用
@Value
注解的方式,需要開啟@RefreshScope
注解(上面沒有演示不開啟這個注解的情況, 建議有興趣的可以自己嘗試一下)
II. 配置變更監聽
既然配置能刷新,那么如果我希望獲取配置變更的事件,然后做一些其他的事情,是否ok呢?
其實進入 ContextRefresher
的源碼,看下refresh接口,就很明確了
public synchronized Set<String> refresh() { Map<String, Object> before = extract( this.context.getEnvironment().getPropertySources()); addConfigFilesToEnvironment(); Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet(); // 注意這一行,拋出了一個變更事件 this.context.publishEvent(new EnvironmentChangeEvent(context, keys)); this.scope.refreshAll(); return keys; }
1. 配置變更監聽
從上面的源碼中,借助spring的事件通知機制,很簡單就可以知道該怎么做了
import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; @Configuration public class EnvConfiguration{ @EventListener public void envListener(EnvironmentChangeEvent event) { System.out.println("conf change: " + event); } }
注意下控制台的輸出即可