前面文章,我们说到回调监听器的方法中,主要就是发布了一个RefreshEvent事件,这个事件主要由 SpringCloud 相关类来处理。今天我们继续分析后续的流程。
RefreshEvent 事件会由 RefreshEventListener 来处理,该 listener 含有一个 ContextRefresher 的对象。
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
handle((ApplicationReadyEvent) event);
}
else if (event instanceof RefreshEvent) {
handle((RefreshEvent) event);
}
}
public void handle(RefreshEvent event) {
if (this.ready.get()) { // don't handle events before app is ready
log.debug("Event received " + event.getEventDesc());
// private ContextRefresher refresh
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
// org.springframework.cloud.context.refresh.ContextRefresher#refresh
public synchronized Set<String> refresh() {
// 刷新Environment环境信息
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
// 添加配置内容到环境对象中
addConfigFilesToEnvironment();
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
// 发布EnvironmentChangeEvent事件(环境变更事件)
this.context.publishEvent(new EnvironmentChangeEvent事件(this.context, keys));
return keys;
}
从源码可以看到,refreshEnvironment 会去刷新 Spring 环境变量,具体刷新思想就是重新创建一个Environment,然后将这个新的环境信息设置到原有的 Spring 环境中。拿到所有变化的配置项后,发布一个环境变化的 EnvironmentChangeEvent(环境变更事件)事件。
ConfigurationPropertiesRebinder 会监听 EnvironmentChangeEvent 事件,监听到事件后会对所有的标注有 ConfigurationProperties 注解的配置类进行销毁后重新初始化的操作,完成后我们的配置类中的属性就是最新的了。
// org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
rebind();
}
}
// 所有的标注有 ConfigurationProperties 注解的配置类
private ConfigurationPropertiesBeans beans;
public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
this.beans = beans;
}
public void rebind() {
this.errors.clear();
for (String name : this.beans.getBeanNames()) {
rebind(name);
}
}
public boolean rebind(String name) {
if (!this.beans.getBeanNames().contains(name)) {
return false;
}
if (this.applicationContext != null) {
try {
Object bean = this.applicationContext.getBean(name);
if (AopUtils.isAopProxy(bean)) {
bean = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
// TODO: determine a more general approach to fix this.
// see https://github.com/spring-cloud/spring-cloud-commons/issues/571
if (getNeverRefreshable().contains(bean.getClass().getName())) {
return false; // ignore
}
// 销毁bean
this.applicationContext.getAutowireCapableBeanFactory()
.destroyBean(bean);
// 重新初始化bean
this.applicationContext.getAutowireCapableBeanFactory()
.initializeBean(bean, name);
return true;
}
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
catch (Exception e) {
this.errors.put(name, e);
throw new IllegalStateException("Cannot rebind to " + name, e);
}
}
return false;
}
上述代码只会对标有 ConfigurationProperties 注解的配置类进行rebind,那对于普通组件类里标有 @Value 注解的属性要怎么生效呢?这个其实需要配合 @RefreshScope 注解来生效的。
我们继续回到前面处理RefreshEvent事件的ContextRefresher#refresh()方法,接着会有一步 refreshAll 的操作,会调用父类的destroy()方法。
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
// private RefreshScope scope;
this.scope.refreshAll();
return keys;
}
// org.springframework.cloud.context.scope.refresh.RefreshScope#refreshAll
public void refreshAll() {
// 调用父类的销毁方法:org.springframework.cloud.context.scope.GenericScope#destroy()
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
父类就是 GenericScope,我们知道 Spring 中的 Bean 是有Scope 的概念的,Spring 默认 Scope 有单例和原型两种,同时提供了 Scope 扩展接口,通过实现该接口我们可以定义自己的 Scope。
在Spring IOC容器初始化的时候,doGetBean()方法中,这些自定义 Scope 类型对象的管理会交给相应的 Scope 实现去管理。
SpringCloud 实现的 RefreshScope 就是用来在运行时动态刷新 Bean 用的,RefreshScope 继承 GenericScope,提供 get()和 destroy()方法。
回到refreshAll()方法,在 refreshAll()中调用 super.destroy()方法时会将该 scope 的这些 Bean 都销毁掉,在下次 get()的时候会重新新触发spring的createBean,创建出一个新的bean对象,新创建的 Bean 就有了我们最新的配置。
// org.springframework.cloud.context.scope.GenericScope#get
public Object get(String name, ObjectFactory<?> objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 会重新触发spring的createBean,创建出一个新的bean对象,填充进去的属性就是最新配置的内容
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
至此,我们就实现了配置的热更新。