天天看點

Nacos源碼分析十、配置動态重新整理(3)

前文提到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是所有的配置屬性對象

Nacos源碼分析十、配置動态重新整理(3)

周遊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自身的東西比較多,簡單整理一下:

  1. @RefreshScope和RefreshScope的作用
    1. 首先被@RefreshScope注解的bean會cglib動态代理。實際上@Scope就會被代理。這是Spring部分的内容,預設會添加一個DelegatingIntroductionInterceptor增強器。目前分析過程不需要關注這個增強器,因為對于@RefreshScope定義的重新整理域來說,又在前面加了一個新的增強器,這個增強器直接反射原方法就傳回了,也就是把DelegatingIntroductionInterceptor屏蔽了。

      – 為什麼要這麼做呢? 實際上spring-cloud就是擴充了Scope作用域,定義了一個重新整理域。是以使用了spring核心提供的擴充域的架構,但是不需要對原功能進行增強,是以加了一個增強器直接把下層的屏蔽了。

      Nacos源碼分析十、配置動态重新整理(3)
      實際上需要用到這個Scope的動态代理目的是為了每次都能通過getTarget獲得最新的被代理對象:
      Nacos源碼分析十、配置動态重新整理(3)
    2. RefreshScope管理了被@RefreshScope注解定義的bean的生命周期,提供了get(建立)、destory(銷毀)方法。
  2. nacos通過釋出RefreshEvent事件通知spring-cloud進行重新整理操作,spring-cloud監聽到事件後做兩件事:
    1. 重新整理屬性源–屬性源相對應的屬性bean從舊的換成新的
    2. 觸發scope的refreshAll操作,針對RefreshScope來說就是清空了他所管理的緩存bean,待再次調用時重新建立,建立過程就會注入新的屬性源