配置属性动态刷新
在微服务环境下,配置中心更改了配置属性后,我们为了能在对应项目上实现动态刷新,常用二种方式
1、使用属性注解
//AbcConfig.java
@Configuration
@EnableConfigurationProperties({AbcProperties.class})
public class AbcConfig {
}
//AbcProperties.java
@ConfigurationProperties(prefix = "pro")
public class AbcProperties {
public AbcProperties(){
System.out.println("------------AbcProperties--------------");
}
private String abc;
public String getAbc() {
return abc;
}
public void setAbc(String abc) {
this.abc = abc;
}
@PreDestroy
public void destroy(){
System.out.println("------------AbcProperties---destroy-----------");
}
}
2、类作用域注解 @RefreshScope
@Component
@RefreshScope
public class DefProperties{
public DefProperties(){
System.out.println("------------DefProperties--------------");
}
/** 名称 */
@Value("${pro.def}")
private String def;
public String getDef() {
return def;
}
public void setDef(String def) {
this.def = def;
}
@PreDestroy
public void destroy(){
System.out.println("------------DefProperties---destroy-----------");
}
本文使用nacos作为配置中心,内容如下
pro:
abc: 11
def: 22
我们先测试 AbcProperties类,更改nacos配置中的abc值。
在nacos的客户端有个线程(ClientWorker.LongPollingRunnable)不断检测本地标识和服务端标识是否一致(md5),不一致就发布刷新事件。
//这里只截部分关键代码,感兴趣的可自己调试下
public class NacosContextRefresher{
private void registerNacosListener(final String group, final String dataId) {
...
//最终在这里发布刷新事件
NacosContextRefresher.this.applicationContext.publishEvent(new RefreshEvent(this, (Object)null, "Refresh Nacos config"));
...
}
}
之后就是spring cloud 的刷新逻辑了
//这个类接收到前面nacos发布出来的 RefreshEvent 刷新事件
public class RefreshEventListener implements SmartApplicationListener {
private static Log log = LogFactory.getLog(RefreshEventListener.class);
private ContextRefresher refresh;
private AtomicBoolean ready = new AtomicBoolean(false);
public RefreshEventListener(ContextRefresher refresh) {
this.refresh = refresh;
}
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationReadyEvent.class.isAssignableFrom(eventType) || RefreshEvent.class.isAssignableFrom(eventType);
}
//接收事件
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
this.handle((ApplicationReadyEvent)event);
} else if (event instanceof RefreshEvent) {
//处理刷新事件
this.handle((RefreshEvent)event);
}
}
public void handle(ApplicationReadyEvent event) {
this.ready.compareAndSet(false, true);
}
public void handle(RefreshEvent event) {
if (this.ready.get()) {
log.debug("Event received " + event.getEventDesc());
//处理刷新,且返回那些key被刷新过
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
}
//上下文刷新类,环境变量的更新 和 RefreshScope注解类的更新,都调用到之类
public class ContextRefresher {
//前面的this.refresh.refresh(); 将调用到这里
public synchronized Set<String> refresh() {
// 环境变量刷新
Set<String> keys = this.refreshEnvironment();
// RefreshScope 刷新
this.scope.refreshAll();
//返回更改过的keys
return keys;
}
public synchronized Set<String> refreshEnvironment() {
//罗列出当前所有环境变量
Map<String, Object> before = this.extract(this.context.getEnvironment().getPropertySources());
//加载所有的配置文件包括服务端
this.addConfigFilesToEnvironment();
//然后把刷新前后的环境变量做比较,看看那些key发生了变化(返回打印用)
Set<String> keys = this.changes(before, this.extract(this.context.getEnvironment().getPropertySources())).keySet();
//发布个环境变量更新事件,触发环境变量更新逻辑
//重绑定到对应的类上,比如之前的 AbcProperties
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
}
public class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
//所有的属性类都刷一遍
@ManagedOperation
public void rebind() {
this.errors.clear();
Iterator var1 = this.beans.getBeanNames().iterator();
while(var1.hasNext()) {
String name = (String)var1.next();
//根据bean名 刷对应的bean
this.rebind(name);
}
}
@ManagedOperation
public boolean rebind(String name) {
if (!this.beans.getBeanNames().contains(name)) {
return false;
} else {
if (this.applicationContext != null) {
try {
Object bean = this.applicationContext.getBean(name);
if (AopUtils.isAopProxy(bean)) {
bean = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
if (this.getNeverRefreshable().contains(bean.getClass().getName())) {
return false;
}
//先调用bean的 destroy()方法,
this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
//重新初始化bean ,(**不是实例化**)
this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
return true;
}
} catch (RuntimeException var3) {
this.errors.put(name, var3);
throw var3;
} catch (Exception var4) {
this.errors.put(name, var4);
throw new IllegalStateException("Cannot rebind to " + name, var4);
}
}
return false;
}
}
// 接收到 前面发布的EnvironmentChangeEvent事件,然后走rebind();
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource()) || event.getKeys().equals(event.getSource())) {
this.rebind();
}
}
执行完后,属性类的字段值就被重新赋值了,类还是原来的类,值不一样了。
我们在看看 RefreshScope 是怎么做的
在测试 DefProperties类,更改nacos配置中的def值。
前面都一样,一直到
public class ContextRefresher {
//前面的this.refresh.refresh(); 将调用到这里
public synchronized Set<String> refresh() {
// 环境变量刷新
Set<String> keys = this.refreshEnvironment();
// RefreshScope 刷新 这次我们看这里
this.scope.refreshAll();
//返回更改过的keys
return keys;
}
}
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
//前面this.scope.refreshAll(); 调用到这里
public void refreshAll() {
//这个方法会清理 作用域为 refresh 的bean工厂和bean缓存 ,且调用bean的 destroy()
//对象名为GenericScope.BeanLifecycleWrapper
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
}
到此 刷新事件结束,BeanLifecycleWrapper中缓存的这个bean信息了清理,但ico中它的代理类还在。怎么办?此时,如果那个地方用到了这个bean。spring 通过getBean方法获取到他的代理类,代理类 内部 在去getBean,重新去实例化它 且缓存在GenericScope.BeanLifecycleWrapper 中!
代理类获取bean示例:
class CglibAopProxy implements AopProxy, Serializable {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
TargetSource targetSource = this.advised.getTargetSource();
//获取真实类
target = targetSource.getTarget();
}
}
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
public SimpleBeanTargetSource() {
}
//获取到真实bean
public Object getTarget() throws Exception {
return this.getBeanFactory().getBean(this.getTargetBeanName());
}
}
//scope生成bean
注意下scope 对象,和beanName(这是一个代理bean名称),表达式是一个回调
我们看下他调到了哪里?
//这是 RefreshScope 的父类
class GenericScope{
//前面调到了这里,放cache缓存中放 bean名和 生产对象
public Object get(String name, ObjectFactory<?> objectFactory) {
GenericScope.BeanLifecycleWrapper value = this.cache.put(name, new GenericScope.BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 这里会调到 objectFactory.getObject(); 然后在缓存中就有了真实bean
return value.getBean();
} catch (RuntimeException var5) {
this.errors.put(name, var5);
throw var5;
}
}
}
调用完成后
到此刷新完成了。这里的bean就是真实bean了。