本篇将介绍并演示如何实现配置信息的刷新,但不会涉及到底层的实现原理,想要探究里面的神奇,可以网上google一下,或者期待后续的源码分析篇
我们这里主要借助这个类来实现配置刷新,至于从哪里捞出来的这个东西,从Spring-Cloud-Config出发,看了下它怎么玩的,然后依葫芦画瓢
这个类全路径为 org.springframework.cloud.context.refresh.ContextRefresher
,因此你的SpringBoot项目需要做一点修改
<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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
接下来就简单了,直接调用这个类的refresh()
方法就可以了,just so easy~
配置文件: application.yml
biz:
refresh: ${random.long}
key: refresh-test
rest:
uuid: ${random.uuid}
server:
port: 8081
读取配置的bean,演示了两种获取方式,分别如下
@Data
@Component
@ConfigurationProperties(prefix = "biz")
public class BizConfig {
private String key;
private Long refresh;
}
开启刷新的@Value
注解方式,注意下面的@RefreshScoe
注解,这个必须有,负责更新后的配置不会同步
@Data
@RefreshScope
@Component
public class ValueConfig {
@Value("${rest.uuid}")
private String uuid;
}
测试Controller
如下
@RestController
public class DemoController {
@Autowired
private ContextRefresher contextRefresher;
@Autowired
private BizConfig bizConfig;
@Autowired
private ValueConfig valueConfig;
@GetMapping(path = "/show")
public String show() {
JSONObject res = new JSONObject();
res.put("biz", JSONObject.toJSONString(bizConfig));
res.put("uuid", valueConfig.getUuid());
return res.toJSONString();
}
@GetMapping(path = "/refresh")
public String refresh() {
new Thread(() -> contextRefresher.refresh()).start();
return show();
}
}
启动上面的应用,然后开启愉快的测试,调用refresh接口,发现每次的返回都不一样(因为配置文件使用了random随机生成),但是访问show接口时,每次返回的都是一样的,也就是说refresh接口中确实实现了配置的刷新
说明
ConfigurationProperties
方式获取注解时,自动支持刷新配置@Value
注解的方式,需要开启@RefreshScope
注解(上面没有演示不开启这个注解的情况, 建议有兴趣的可以自己尝试一下)既然配置能刷新,那么如果我希望获取配置变更的事件,然后做一些其他的事情,是否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;
}
从上面的源码中,借助spring的事件通知机制,很简单就可以知道该怎么做了,来一个简单的demo,这里顺带测试下上面漏掉的不刷新的场景
@RestController
public class DemoController {
@Autowired
private ContextRefresher contextRefresher;
@Autowired
private BizConfig bizConfig;
@Autowired
private ValueConfig valueConfig;
@Value("${rest.uuid}")
private String uuid;
@GetMapping(path = "/show")
public String show() {
JSONObject res = new JSONObject();
res.put("biz", JSONObject.toJSONString(bizConfig));
res.put("uuid", valueConfig.getUuid());
res.put("no-refresh", uuid);
return res.toJSONString();
}
@GetMapping(path = "/refresh")
public String refresh() {
new Thread(() -> contextRefresher.refresh()).start();
return show();
}
@EventListener
public void envListener(EnvironmentChangeEvent event) {
System.out.println("conf change: " + event);
}
}
直接将Listener写在Controller类内部... 原则上不推荐上面的写法
依然来个实测,主要注意下控制台的输出即可