前文提到RefreshScope中維護了一個map緩存,緩存的内容是包裝原bean的BeanLifecycleWrapper,這個包裝類具備銷毀能力。當新的配置更新通知來後,我們隻要能找到RefreshScope去銷毀了對應的bean,那麼再次使用時cglib的proxy就會重新去擷取target類執行個體,然後重新執行個體化。
回到NacosContextRefresher類,看一下注冊監聽的代碼:
private void registerNacosListener(final String groupKey, final String dataKey) {
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
// todo feature: support single refresh for listening
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug(String.format(
"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
group, dataId, configInfo));
}
}
});
try {
configService.addListener(dataKey, groupKey, listener);
}
catch (NacosException e) {
log.warn(String.format(
"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
groupKey), e);
}
}
當有配置變更時釋出RefreshEvent事件,這個事件是RefreshEventListener監聽的:
@Override
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());
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
跟進去:
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
先進行環境的重新整理:
public synchronized Set<String> refreshEnvironment() {
//擷取老的配置屬性源
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
//重新整理配置檔案
addConfigFilesToEnvironment();
//擷取修改的屬性源key
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
//通知事件,通知配置屬性對象去重新加載屬性
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
addConfigFilesToEnvironment方法:
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
//建立新的環境去接受新配置
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
//建立新的環境去加載配置
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
.environment(environment);
// Just the listeners that affect the environment (e.g. excluding logging
// listener because it has side effects)
//添加跟配置檔案加載有關的
builder.application()
.setListeners(Arrays.asList(new BootstrapApplicationListener(),
new ConfigFileApplicationListener()));
capture = builder.run();
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
MutablePropertySources target = this.context.getEnvironment()
.getPropertySources();
String targetName = null;
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (target.contains(name)) {
targetName = name;
}
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
//有同名的就替換老的屬性源
target.replace(name, source);
}
else {
if (targetName != null) {
target.addAfter(targetName, source);
}
else {
// targetName was null so we are at the start of the list
target.addFirst(source);
targetName = name;
}
}
}
}
}
finally {
ConfigurableApplicationContext closeable = capture;
while (closeable != null) {
try {
closeable.close();
}
catch (Exception e) {
// Ignore;
}
if (closeable.getParent() instanceof ConfigurableApplicationContext) {
closeable = (ConfigurableApplicationContext) closeable.getParent();
}
else {
break;
}
}
}
return capture;
}
changes擷取新老之間有差異的屬性源key集合:
private Map<String, Object> changes(Map<String, Object> before,
Map<String, Object> after) {
Map<String, Object> result = new HashMap<String, Object>();
for (String key : before.keySet()) {
if (!after.containsKey(key)) {
result.put(key, null);
}
else if (!equal(before.get(key), after.get(key))) {
result.put(key, after.get(key));
}
}
for (String key : after.keySet()) {
if (!before.containsKey(key)) {
result.put(key, after.get(key));
}
}
return result;
}
然後釋出EnvironmentChangeEvent事件,這個事件是ConfigurationPropertiesRebinder監聽的:
public class ConfigurationPropertiesRebinder
implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
...
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
rebind();
}
}
...
}
然後是rebind方法:
public void rebind() {
this.errors.clear();
for (String name : this.beans.getBeanNames()) {
rebind(name);
}
}
這裡的beans是所有的配置屬性對象
周遊rebind:
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
}
this.applicationContext.getAutowireCapableBeanFactory()
.destroyBean(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;
}
實際上就是先銷毀,然後重新初始化。
回去看scope做refresh操作。這個scope就是我們的RefreshScope。再跟進去:
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
父類的destroy方法:
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
把所有的wrapper取出來,周遊調用destory方法:
public void destroy() {
if (this.callback == null) {
return;
}
synchronized (this.name) {
Runnable callback = this.callback;
if (callback != null) {
callback.run();
}
this.callback = null;
this.bean = null;
}
}
貌似沒有做什麼,實際上核心代碼就是最後一行 this.bean = null;将被包裝的bean設定為null了,這樣下次取的時候發現是null就會再次建立:
public Object getBean() {
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}
總結
至此nacos的配置動态重新整理已經基本分析完了。牽扯到spring自身的東西比較多,簡單整理一下:
- @RefreshScope和RefreshScope的作用
-
首先被@RefreshScope注解的bean會cglib動态代理。實際上@Scope就會被代理。這是Spring部分的内容,預設會添加一個DelegatingIntroductionInterceptor增強器。目前分析過程不需要關注這個增強器,因為對于@RefreshScope定義的重新整理域來說,又在前面加了一個新的增強器,這個增強器直接反射原方法就傳回了,也就是把DelegatingIntroductionInterceptor屏蔽了。
– 為什麼要這麼做呢? 實際上spring-cloud就是擴充了Scope作用域,定義了一個重新整理域。是以使用了spring核心提供的擴充域的架構,但是不需要對原功能進行增強,是以加了一個增強器直接把下層的屏蔽了。
實際上需要用到這個Scope的動态代理目的是為了每次都能通過getTarget獲得最新的被代理對象: - RefreshScope管理了被@RefreshScope注解定義的bean的生命周期,提供了get(建立)、destory(銷毀)方法。
-
- nacos通過釋出RefreshEvent事件通知spring-cloud進行重新整理操作,spring-cloud監聽到事件後做兩件事:
- 重新整理屬性源–屬性源相對應的屬性bean從舊的換成新的
- 觸發scope的refreshAll操作,針對RefreshScope來說就是清空了他所管理的緩存bean,待再次調用時重新建立,建立過程就會注入新的屬性源