前面文章,我們說到回調監聽器的方法中,主要就是發布了一個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 readylog.debug("Event received " + event.getEventDesc());// private ContextRefresher refreshSet<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/571if (getNeverRefreshable().contains(bean.getClass().getName())) {return false; // ignore}// 銷毀beanthis.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);// 重新初始化beanthis.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;}
}
至此,我們就實現了配置的熱更新。