Nacos源码分析十、配置动态刷新(3)

x33g5p2x  于2021-12-20 转载在 其他  
字(7.2k)|赞(0)|评价(0)|浏览(368)

前文提到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自身的东西比较多,简单整理一下:

  1. @RefreshScope和RefreshScope的作用

  2. 首先被@RefreshScope注解的bean会cglib动态代理。实际上@Scope就会被代理。这是Spring部分的内容,默认会添加一个DelegatingIntroductionInterceptor增强器。当前分析过程不需要关注这个增强器,因为对于@RefreshScope定义的刷新域来说,又在前面加了一个新的增强器,这个增强器直接反射原方法就返回了,也就是把DelegatingIntroductionInterceptor屏蔽了。

– 为什么要这么做呢? 实际上spring-cloud就是扩展了Scope作用域,定义了一个刷新域。因此使用了spring核心提供的扩展域的框架,但是不需要对原功能进行增强,所以加了一个增强器直接把下层的屏蔽了。

实际上需要用到这个Scope的动态代理目的是为了每次都能通过getTarget获得最新的被代理对象:

  1. RefreshScope管理了被@RefreshScope注解定义的bean的生命周期,提供了get(创建)、destory(销毁)方法。

  2. nacos通过发布RefreshEvent事件通知spring-cloud进行刷新操作,spring-cloud监听到事件后做两件事:

  3. 刷新属性源–属性源相对应的属性bean从旧的换成新的

  4. 触发scope的refreshAll操作,针对RefreshScope来说就是清空了他所管理的缓存bean,待再次调用时重新创建,创建过程就会注入新的属性源

相关文章