当前位置:   article > 正文

spring cloud @RefreshScope 刷新机制_[fixed-10.51.11.38_8848-uat] [data-received] datai

[fixed-10.51.11.38_8848-uat] [data-received] dataid=orderplycloudservice-uat

在学习 nacos 的配置修改发现用到了 @RefreshScope 注解,将 spring boot 的日志调整如下

  1. logging:
  2. level:
  3. com:
  4. alibaba:
  5. cloud: debug
  6. org:
  7. springframework:
  8. context: debug
  9. cloud: debug

调用 nacos 的配置修改,看到如下信息

  1. 2023-03-10 15:48:15.332 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] com.alibaba.nacos.client.config.impl.ClientWorker Caller+0 at com.alibaba.nacos.client.config.impl.ClientWorker.parseUpdateDataIdResponse(ClientWorker.java:486)
  2. - [fixed-node1.hahaou.cn_8848] [polling-resp] config changed. dataId=soft-jraft-apache-derby-config-test.yaml, group=DEFAULT_GROUP
  3. 2023-03-10 15:48:15.333 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] com.alibaba.nacos.client.config.impl.ClientWorker Caller+0 at com.alibaba.nacos.client.config.impl.ClientWorker$LongPollingRunnable.run(ClientWorker.java:598)
  4. - get changedGroupKeys:[soft-jraft-apache-derby-config-test.yaml+DEFAULT_GROUP]
  5. 2023-03-10 15:48:15.400 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] com.alibaba.nacos.client.config.impl.ClientWorker Caller+0 at com.alibaba.nacos.client.config.impl.ClientWorker$LongPollingRunnable.run(ClientWorker.java:616)
  6. - [fixed-node1.hahaou.cn_8848] [data-received] dataId=soft-jraft-apache-derby-config-test.yaml, group=DEFAULT_GROUP, tenant=null, md5=5f214678315ac83144e77f4c4b3b3416, content=spring:
  7. youxia:
  8. config:
  9. name: test, type=
  10. 2023-03-10 15:48:15.400 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] com.alibaba.nacos.client.config.impl.CacheData Caller+0 at com.alibaba.nacos.client.config.impl.CacheData$1.run(CacheData.java:199)
  11. - [fixed-node1.hahaou.cn_8848] [notify-context] dataId=soft-jraft-apache-derby-config-test.yaml, group=DEFAULT_GROUP, md5=5f214678315ac83144e77f4c4b3b3416
  12. 2023-03-10 15:48:15.401 DEBUG [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] o.s.cloud.endpoint.event.RefreshEventListener Caller+0 at org.springframework.cloud.endpoint.event.RefreshEventListener.handle(RefreshEventListener.java:71)
  13. - Event received Refresh Nacos config
  14. 2023-03-10 15:48:17.002 DEBUG [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] o.s.c.a.AnnotationConfigApplicationContext Caller+0 at org.springframework.context.support.AbstractApplicationContext.prepareRefresh(AbstractApplicationContext.java:596)
  15. - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@653cd99a
  16. 2023-03-10 15:48:18.632 DEBUG [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] c.a.cloud.nacos.client.NacosPropertySourceBuilder Caller+0 at com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder.loadNacosData(NacosPropertySourceBuilder.java:93)
  17. - Loading nacos data, dataId: 'soft-jraft-apache-derby-config-test.yaml', group: 'DEFAULT_GROUP', data: spring:
  18. youxia:
  19. config:
  20. name: test
  21. 2023-03-10 15:48:18.706 WARN [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] c.a.cloud.nacos.client.NacosPropertySourceBuilder Caller+0 at com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder.loadNacosData(NacosPropertySourceBuilder.java:87)
  22. - Ignore the empty nacos configuration and get it based on dataId[soft-jraft-apache-derby-config] & group[DEFAULT_GROUP]
  23. 2023-03-10 15:48:18.789 WARN [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] c.a.cloud.nacos.client.NacosPropertySourceBuilder Caller+0 at com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder.loadNacosData(NacosPropertySourceBuilder.java:87)
  24. - Ignore the empty nacos configuration and get it based on dataId[soft-jraft-apache-derby-config.properties] & group[DEFAULT_GROUP]
  25. 2023-03-10 15:48:18.790 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] o.s.c.b.c.PropertySourceBootstrapConfiguration Caller+0 at org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration.initialize(PropertySourceBootstrapConfiguration.java:112)
  26. - Located property source: [BootstrapPropertySource@1783236684 {name='bootstrapProperties-soft-jraft-apache-derby-config.properties,DEFAULT_GROUP', properties={}}, BootstrapPropertySource@942001677 {name='bootstrapProperties-soft-jraft-apache-derby-config,DEFAULT_GROUP', properties={}}, BootstrapPropertySource@1637255792 {name='bootstrapProperties-soft-jraft-apache-derby-config-test.yaml,DEFAULT_GROUP', properties={spring.youxia.config.name=test}}]
  27. 2023-03-10 15:48:18.800 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] org.springframework.boot.SpringApplication Caller+0 at org.springframework.boot.SpringApplication.logStartupProfileInfo(SpringApplication.java:651)
  28. - No active profile set, falling back to default profiles: default
  29. 2023-03-10 15:48:18.801 DEBUG [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] o.s.c.a.AnnotationConfigApplicationContext Caller+0 at org.springframework.context.support.AbstractApplicationContext.prepareRefresh(AbstractApplicationContext.java:596)
  30. - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6709b8b
  31. 2023-03-10 15:48:18.806 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] org.springframework.boot.SpringApplication Caller+0 at org.springframework.boot.StartupInfoLogger.logStarted(StartupInfoLogger.java:61)
  32. - Started application in 3.403 seconds (JVM running for 54.758)
  33. 2023-03-10 15:48:18.807 DEBUG [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] o.s.c.a.AnnotationConfigApplicationContext Caller+0 at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1006)
  34. - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6709b8b, started on Fri Mar 10 15:48:18 CST 2023, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@653cd99a
  35. 2023-03-10 15:48:18.808 DEBUG [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] o.s.c.a.AnnotationConfigApplicationContext Caller+0 at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1006)
  36. - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@653cd99a, started on Fri Mar 10 15:48:17 CST 2023
  37. 2023-03-10 15:48:18.819 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] o.s.cloud.endpoint.event.RefreshEventListener Caller+0 at org.springframework.cloud.endpoint.event.RefreshEventListener.handle(RefreshEventListener.java:73)
  38. - Refresh keys changed: [spring.youxia.config.name]
  39. 2023-03-10 15:48:18.820 DEBUG [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] c.a.cloud.nacos.refresh.NacosContextRefresher Caller+0 at com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1.innerReceive(NacosContextRefresher.java:136)
  40. - Refresh Nacos config group=DEFAULT_GROUP,dataId=soft-jraft-apache-derby-config-test.yaml,configInfo=spring:
  41. youxia:
  42. config:
  43. name: test
  44. 2023-03-10 15:48:18.820 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] com.alibaba.nacos.client.config.impl.CacheData Caller+0 at com.alibaba.nacos.client.config.impl.CacheData$1.run(CacheData.java:222)
  45. - [fixed-node1.hahaou.cn_8848] [notify-ok] dataId=soft-jraft-apache-derby-config-test.yaml, group=DEFAULT_GROUP, md5=5f214678315ac83144e77f4c4b3b3416, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@59af462e
  46. 2023-03-10 15:48:18.820 INFO [com.alibaba.nacos.client.Worker.longPolling.fixed-node1.hahaou.cn_8848] com.alibaba.nacos.client.config.impl.CacheData Caller+0 at com.alibaba.nacos.client.config.impl.CacheData.safeNotifyListener(CacheData.java:248)
  47. - [fixed-node1.hahaou.cn_8848] [notify-listener] time cost=3420ms in ClientWorker, dataId=soft-jraft-apache-derby-config-test.yaml, group=DEFAULT_GROUP, md5=5f214678315ac83144e77f4c4b3b3416, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@59af462e
  48. 使用Spring Cloud Alibaba接入Nacos配置中心,获取配置信息name为:test
  49. 使用Spring Cloud Alibaba接入Nacos配置中心,获取配置信息value为:null

得知保存配置后进行了 jvm 重启。

梳理过程如下

@RefreshScope

  1. package org.springframework.cloud.context.config.annotation;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. import org.springframework.context.annotation.Scope;
  8. import org.springframework.context.annotation.ScopedProxyMode;
  9. /**
  10. * Convenience annotation to put a <code>@Bean</code> definition in
  11. * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}.
  12. * Beans annotated this way can be refreshed at runtime and any components that are using
  13. * them will get a new instance on the next method call, fully initialized and injected
  14. * with all dependencies.
  15. *
  16. * @author Dave Syer
  17. *
  18. */
  19. @Target({ ElementType.TYPE, ElementType.METHOD })
  20. @Retention(RetentionPolicy.RUNTIME)
  21. @Scope("refresh")
  22. @Documented
  23. public @interface RefreshScope {
  24. /**
  25. * @see Scope#proxyMode()
  26. * @return proxy mode
  27. */
  28. ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
  29. }

可以得知,@RefreshScope 是一个 scopeName 为 refresh 的 @Scope。

ScopedProxyMode

  1. package org.springframework.context.annotation;
  2. /**
  3. * Enumerates the various scoped-proxy options.
  4. *
  5. * <p>For a more complete discussion of exactly what a scoped proxy is, see the
  6. * section of the Spring reference documentation entitled '<em>Scoped beans as
  7. * dependencies</em>'.
  8. *
  9. * @author Mark Fisher
  10. * @since 2.5
  11. * @see ScopeMetadata
  12. */
  13. public enum ScopedProxyMode {
  14. /**
  15. * Default typically equals {@link #NO}, unless a different default
  16. * has been configured at the component-scan instruction level.
  17. */
  18. DEFAULT,
  19. /**
  20. * Do not create a scoped proxy.
  21. * <p>This proxy-mode is not typically useful when used with a
  22. * non-singleton scoped instance, which should favor the use of the
  23. * {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it
  24. * is to be used as a dependency.
  25. */
  26. NO,
  27. /**
  28. * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by
  29. * the class of the target object.
  30. */
  31. INTERFACES,
  32. /**
  33. * Create a class-based proxy (uses CGLIB).
  34. */
  35. TARGET_CLASS
  36. }

由枚举 ScopedProxyMode 得知,ScopedProxyMode.TARGET_CLASS 通过 cglib 生成一个代理类进行字节码增强

RefreshAutoConfiguration

部分源码如下

  1. package org.springframework.cloud.autoconfigure;
  2. import java.util.HashSet;
  3. import java.util.Set;
  4. import javax.annotation.PostConstruct;
  5. import org.springframework.aop.scope.ScopedProxyUtils;
  6. import org.springframework.beans.BeansException;
  7. import org.springframework.beans.factory.BeanFactory;
  8. import org.springframework.beans.factory.ListableBeanFactory;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.beans.factory.config.BeanDefinition;
  11. import org.springframework.beans.factory.config.BeanDefinitionHolder;
  12. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  13. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  14. import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
  15. import org.springframework.boot.autoconfigure.AutoConfigureBefore;
  16. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  17. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  18. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  19. import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
  20. import org.springframework.boot.context.properties.bind.Bindable;
  21. import org.springframework.boot.context.properties.bind.Binder;
  22. import org.springframework.cloud.context.refresh.ContextRefresher;
  23. import org.springframework.cloud.context.scope.refresh.RefreshScope;
  24. import org.springframework.cloud.endpoint.event.RefreshEventListener;
  25. import org.springframework.cloud.logging.LoggingRebinder;
  26. import org.springframework.context.ConfigurableApplicationContext;
  27. import org.springframework.context.EnvironmentAware;
  28. import org.springframework.context.annotation.Bean;
  29. import org.springframework.context.annotation.Configuration;
  30. import org.springframework.context.weaving.LoadTimeWeaverAware;
  31. import org.springframework.core.env.Environment;
  32. import org.springframework.core.env.StandardEnvironment;
  33. import org.springframework.instrument.classloading.LoadTimeWeaver;
  34. import org.springframework.stereotype.Component;
  35. import org.springframework.util.StringUtils;
  36. /**
  37. * Autoconfiguration for the refresh scope and associated features to do with changes in
  38. * the Environment (e.g. rebinding logger levels).
  39. *
  40. * @author Dave Syer
  41. * @author Venil Noronha
  42. */
  43. @Configuration(proxyBeanMethods = false)
  44. @ConditionalOnClass(RefreshScope.class)
  45. @ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED,
  46. matchIfMissing = true)
  47. @AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
  48. public class RefreshAutoConfiguration {
  49. /**
  50. * Name of the refresh scope name.
  51. */
  52. public static final String REFRESH_SCOPE_NAME = "refresh";
  53. /**
  54. * Name of the prefix for refresh scope.
  55. */
  56. public static final String REFRESH_SCOPE_PREFIX = "spring.cloud.refresh";
  57. /**
  58. * Name of the enabled prefix for refresh scope.
  59. */
  60. public static final String REFRESH_SCOPE_ENABLED = REFRESH_SCOPE_PREFIX + ".enabled";
  61. @Bean
  62. @ConditionalOnMissingBean(RefreshScope.class)
  63. public static RefreshScope refreshScope() {
  64. return new RefreshScope();
  65. }
  66. @Bean
  67. @ConditionalOnMissingBean
  68. public static LoggingRebinder loggingRebinder() {
  69. return new LoggingRebinder();
  70. }
  71. @Bean
  72. @ConditionalOnMissingBean
  73. public ContextRefresher contextRefresher(ConfigurableApplicationContext context,
  74. RefreshScope scope) {
  75. return new ContextRefresher(context, scope);
  76. }
  77. @Bean
  78. public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
  79. return new RefreshEventListener(contextRefresher);
  80. }
  81. }

  1. @ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED,
  2. matchIfMissing = true)

通过上面的注解得知,自动刷新在 spring cloud 中默认启用

nacos中添加配置

NacosConfigService

  1. public NacosConfigService(Properties properties) throws NacosException {
  2. ValidatorUtils.checkInitParam(properties);
  3. String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
  4. if (StringUtils.isBlank(encodeTmp)) {
  5. this.encode = Constants.ENCODE;
  6. } else {
  7. this.encode = encodeTmp.trim();
  8. }
  9. initNamespace(properties);
  10. this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
  11. this.agent.start();
  12. this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
  13. }

NacosConfigService 构造器创建 ClientWorker 对象

ClientWorker

  1. public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,
  2. final Properties properties) {
  3. this.agent = agent;
  4. this.configFilterChainManager = configFilterChainManager;
  5. // Initialize the timeout parameter
  6. init(properties);
  7. this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
  8. @Override
  9. public Thread newThread(Runnable r) {
  10. Thread t = new Thread(r);
  11. t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
  12. t.setDaemon(true);
  13. return t;
  14. }
  15. });
  16. this.executorService = Executors
  17. .newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
  18. @Override
  19. public Thread newThread(Runnable r) {
  20. Thread t = new Thread(r);
  21. t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
  22. t.setDaemon(true);
  23. return t;
  24. }
  25. });
  26. this.executor.scheduleWithFixedDelay(new Runnable() {
  27. @Override
  28. public void run() {
  29. try {
  30. checkConfigInfo();
  31. } catch (Throwable e) {
  32. LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
  33. }
  34. }
  35. }, 1L, 10L, TimeUnit.MILLISECONDS);
  36. }
  37. public void checkConfigInfo() {
  38. // Dispatch taskes.
  39. int listenerSize = cacheMap.size();
  40. // Round up the longingTaskCount.
  41. int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
  42. if (longingTaskCount > currentLongingTaskCount) {
  43. for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
  44. // The task list is no order.So it maybe has issues when changing.
  45. executorService.execute(new LongPollingRunnable(i));
  46. }
  47. currentLongingTaskCount = longingTaskCount;
  48. }
  49. }
  50. class LongPollingRunnable implements Runnable {
  51. private final int taskId;
  52. public LongPollingRunnable(int taskId) {
  53. this.taskId = taskId;
  54. }
  55. @Override
  56. public void run() {
  57. List<CacheData> cacheDatas = new ArrayList<CacheData>();
  58. List<String> inInitializingCacheList = new ArrayList<String>();
  59. try {
  60. // check failover config
  61. for (CacheData cacheData : cacheMap.values()) {
  62. if (cacheData.getTaskId() == taskId) {
  63. cacheDatas.add(cacheData);
  64. try {
  65. checkLocalConfig(cacheData);
  66. if (cacheData.isUseLocalConfigInfo()) {
  67. cacheData.checkListenerMd5();
  68. }
  69. } catch (Exception e) {
  70. LOGGER.error("get local config info error", e);
  71. }
  72. }
  73. }
  74. // check server config
  75. List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
  76. if (!CollectionUtils.isEmpty(changedGroupKeys)) {
  77. LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
  78. }
  79. for (String groupKey : changedGroupKeys) {
  80. String[] key = GroupKey.parseKey(groupKey);
  81. String dataId = key[0];
  82. String group = key[1];
  83. String tenant = null;
  84. if (key.length == 3) {
  85. tenant = key[2];
  86. }
  87. try {
  88. String[] ct = getServerConfig(dataId, group, tenant, 3000L);
  89. CacheData cache = cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));
  90. cache.setContent(ct[0]);
  91. if (null != ct[1]) {
  92. cache.setType(ct[1]);
  93. }
  94. LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
  95. agent.getName(), dataId, group, tenant, cache.getMd5(),
  96. ContentUtils.truncateContent(ct[0]), ct[1]);
  97. } catch (NacosException ioe) {
  98. String message = String
  99. .format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
  100. agent.getName(), dataId, group, tenant);
  101. LOGGER.error(message, ioe);
  102. }
  103. }
  104. for (CacheData cacheData : cacheDatas) {
  105. if (!cacheData.isInitializing() || inInitializingCacheList
  106. .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
  107. cacheData.checkListenerMd5();
  108. cacheData.setInitializing(false);
  109. }
  110. }
  111. inInitializingCacheList.clear();
  112. executorService.execute(this);
  113. } catch (Throwable e) {
  114. // If the rotation training task is abnormal, the next execution time of the task will be punished
  115. LOGGER.error("longPolling error : ", e);
  116. executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
  117. }
  118. }
  119. }

ClientWorker checkConfigInfo() 中通过线程池创建 LongPollingRunnable 对象,线程池名称前缀为 com.alibaba.nacos.client.Worker.longPolling

CacheData

  1. void checkListenerMd5() {
  2. for (ManagerListenerWrap wrap : listeners) {
  3. if (!md5.equals(wrap.lastCallMd5)) {
  4. safeNotifyListener(dataId, group, content, type, md5, wrap);
  5. }
  6. }
  7. }
  8. private void safeNotifyListener(final String dataId, final String group, final String content, final String type,
  9. final String md5, final ManagerListenerWrap listenerWrap) {
  10. final Listener listener = listenerWrap.listener;
  11. Runnable job = new Runnable() {
  12. @Override
  13. public void run() {
  14. ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
  15. ClassLoader appClassLoader = listener.getClass().getClassLoader();
  16. try {
  17. if (listener instanceof AbstractSharedListener) {
  18. AbstractSharedListener adapter = (AbstractSharedListener) listener;
  19. adapter.fillContext(dataId, group);
  20. LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);
  21. }
  22. // 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。
  23. Thread.currentThread().setContextClassLoader(appClassLoader);
  24. ConfigResponse cr = new ConfigResponse();
  25. cr.setDataId(dataId);
  26. cr.setGroup(group);
  27. cr.setContent(content);
  28. configFilterChainManager.doFilter(null, cr);
  29. String contentTmp = cr.getContent();
  30. listener.receiveConfigInfo(contentTmp);
  31. // compare lastContent and content
  32. if (listener instanceof AbstractConfigChangeListener) {
  33. Map data = ConfigChangeHandler.getInstance()
  34. .parseChangeData(listenerWrap.lastContent, content, type);
  35. ConfigChangeEvent event = new ConfigChangeEvent(data);
  36. ((AbstractConfigChangeListener) listener).receiveConfigChange(event);
  37. listenerWrap.lastContent = content;
  38. }
  39. listenerWrap.lastCallMd5 = md5;
  40. LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,
  41. listener);
  42. } catch (NacosException ex) {
  43. LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}",
  44. name, dataId, group, md5, listener, ex.getErrCode(), ex.getErrMsg());
  45. } catch (Throwable t) {
  46. LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId,
  47. group, md5, listener, t.getCause());
  48. } finally {
  49. Thread.currentThread().setContextClassLoader(myClassLoader);
  50. }
  51. }
  52. };
  53. final long startNotify = System.currentTimeMillis();
  54. try {
  55. if (null != listener.getExecutor()) {
  56. listener.getExecutor().execute(job);
  57. } else {
  58. job.run();
  59. }
  60. } catch (Throwable t) {
  61. LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} throwable={}", name, dataId,
  62. group, md5, listener, t.getCause());
  63. }
  64. final long finishNotify = System.currentTimeMillis();
  65. LOGGER.info("[{}] [notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ",
  66. name, (finishNotify - startNotify), dataId, group, md5, listener);
  67. }

调用 CacheData 的 checkListenerMd5()

AbstractSharedListener

  1. public abstract class AbstractSharedListener implements Listener {
  2. private volatile String dataId;
  3. private volatile String group;
  4. public final void fillContext(String dataId, String group) {
  5. this.dataId = dataId;
  6. this.group = group;
  7. }
  8. @Override
  9. public final void receiveConfigInfo(String configInfo) {
  10. innerReceive(dataId, group, configInfo);
  11. }
  12. @Override
  13. public Executor getExecutor() {
  14. return null;
  15. }
  16. /**
  17. * receive.
  18. *
  19. * @param dataId data ID
  20. * @param group group
  21. * @param configInfo content
  22. */
  23. public abstract void innerReceive(String dataId, String group, String configInfo);
  24. }

调用 AbstractSharedListener 的 receiveConfigInfo(),在 NacosContextRefresher 的 registerNacosListener() 中进行实现

NacosContextRefresher

  1. private void registerNacosListener(final String groupKey, final String dataKey) {
  2. String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
  3. Listener listener = listenerMap.computeIfAbsent(key,
  4. lst -> new AbstractSharedListener() {
  5. @Override
  6. public void innerReceive(String dataId, String group,
  7. String configInfo) {
  8. refreshCountIncrement();
  9. nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
  10. // todo feature: support single refresh for listening
  11. applicationContext.publishEvent(
  12. new RefreshEvent(this, null, "Refresh Nacos config"));
  13. if (log.isDebugEnabled()) {
  14. log.debug(String.format(
  15. "Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
  16. group, dataId, configInfo));
  17. }
  18. }
  19. });
  20. try {
  21. configService.addListener(dataKey, groupKey, listener);
  22. }
  23. catch (NacosException e) {
  24. log.warn(String.format(
  25. "register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
  26. groupKey), e);
  27. }
  28. }

最终通过 ApplicationContext 发布事件 RefreshEvent

至此,nacos 逻辑执行完毕。

RefreshEventListener

  1. public class RefreshEventListener implements SmartApplicationListener {
  2. private static Log log = LogFactory.getLog(RefreshEventListener.class);
  3. private ContextRefresher refresh;
  4. private AtomicBoolean ready = new AtomicBoolean(false);
  5. public RefreshEventListener(ContextRefresher refresh) {
  6. this.refresh = refresh;
  7. }
  8. @Override
  9. public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
  10. return ApplicationReadyEvent.class.isAssignableFrom(eventType)
  11. || RefreshEvent.class.isAssignableFrom(eventType);
  12. }
  13. @Override
  14. public void onApplicationEvent(ApplicationEvent event) {
  15. if (event instanceof ApplicationReadyEvent) {
  16. handle((ApplicationReadyEvent) event);
  17. }
  18. else if (event instanceof RefreshEvent) {
  19. handle((RefreshEvent) event);
  20. }
  21. }
  22. public void handle(ApplicationReadyEvent event) {
  23. this.ready.compareAndSet(false, true);
  24. }
  25. public void handle(RefreshEvent event) {
  26. if (this.ready.get()) { // don't handle events before app is ready
  27. log.debug("Event received " + event.getEventDesc());
  28. Set<String> keys = this.refresh.refresh();
  29. log.info("Refresh keys changed: " + keys);
  30. }
  31. }
  32. }

调用 RefreshEventListener 的 onApplicationEvent(),事件对象为 RefreshEvent。

执行完可以看到打印了日志

  1. Event received Refresh Nacos config

后面调用 ContextRefresher 的 refresh()

ContextRefresher

  1. public synchronized Set<String> refresh() {
  2. Set<String> keys = refreshEnvironment();
  3. this.scope.refreshAll();
  4. return keys;
  5. }
  6. public synchronized Set<String> refreshEnvironment() {
  7. Map<String, Object> before = extract(
  8. this.context.getEnvironment().getPropertySources());
  9. addConfigFilesToEnvironment();
  10. Set<String> keys = changes(before,
  11. extract(this.context.getEnvironment().getPropertySources())).keySet();
  12. this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
  13. return keys;
  14. }
  15. /* For testing. */ ConfigurableApplicationContext addConfigFilesToEnvironment() {
  16. ConfigurableApplicationContext capture = null;
  17. try {
  18. StandardEnvironment environment = copyEnvironment(
  19. this.context.getEnvironment());
  20. SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
  21. .bannerMode(Mode.OFF).web(WebApplicationType.NONE)
  22. .environment(environment);
  23. // Just the listeners that affect the environment (e.g. excluding logging
  24. // listener because it has side effects)
  25. builder.application()
  26. .setListeners(Arrays.asList(new BootstrapApplicationListener(),
  27. new ConfigFileApplicationListener()));
  28. capture = builder.run();
  29. if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
  30. environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
  31. }
  32. MutablePropertySources target = this.context.getEnvironment()
  33. .getPropertySources();
  34. String targetName = null;
  35. for (PropertySource<?> source : environment.getPropertySources()) {
  36. String name = source.getName();
  37. if (target.contains(name)) {
  38. targetName = name;
  39. }
  40. if (!this.standardSources.contains(name)) {
  41. if (target.contains(name)) {
  42. target.replace(name, source);
  43. }
  44. else {
  45. if (targetName != null) {
  46. target.addAfter(targetName, source);
  47. // update targetName to preserve ordering
  48. targetName = name;
  49. }
  50. else {
  51. // targetName was null so we are at the start of the list
  52. target.addFirst(source);
  53. targetName = name;
  54. }
  55. }
  56. }
  57. }
  58. }
  59. finally {
  60. ConfigurableApplicationContext closeable = capture;
  61. while (closeable != null) {
  62. try {
  63. closeable.close();
  64. }
  65. catch (Exception e) {
  66. // Ignore;
  67. }
  68. if (closeable.getParent() instanceof ConfigurableApplicationContext) {
  69. closeable = (ConfigurableApplicationContext) closeable.getParent();
  70. }
  71. else {
  72. break;
  73. }
  74. }
  75. }
  76. return capture;
  77. }

refreshEnvironment() 中执行如下操作

addConfigFilesToEnvironment() 添加到配置文件到环境中,发布一系列事件 BootstrapApplicationListener、ConfigFileApplicationListener

调用 EventPublishingRunListener 的发布一系列事件进行 jvm 的重启相关操作,其中 EventPublishingRunListener 是默认的监听器,在 spring boot 的 META-INF/spring.factories 中进行了指定

  1. # Run Listeners
  2. org.springframework.boot.SpringApplicationRunListener=\
  3. org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener

  1. public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
  2. private final SpringApplication application;
  3. private final String[] args;
  4. private final SimpleApplicationEventMulticaster initialMulticaster;
  5. public EventPublishingRunListener(SpringApplication application, String[] args) {
  6. this.application = application;
  7. this.args = args;
  8. this.initialMulticaster = new SimpleApplicationEventMulticaster();
  9. for (ApplicationListener<?> listener : application.getListeners()) {
  10. this.initialMulticaster.addApplicationListener(listener);
  11. }
  12. }
  13. @Override
  14. public int getOrder() {
  15. return 0;
  16. }
  17. @Override
  18. public void starting() {
  19. this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
  20. }
  21. @Override
  22. public void environmentPrepared(ConfigurableEnvironment environment) {
  23. this.initialMulticaster
  24. .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
  25. }
  26. @Override
  27. public void contextPrepared(ConfigurableApplicationContext context) {
  28. this.initialMulticaster
  29. .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
  30. }
  31. @Override
  32. public void contextLoaded(ConfigurableApplicationContext context) {
  33. for (ApplicationListener<?> listener : this.application.getListeners()) {
  34. if (listener instanceof ApplicationContextAware) {
  35. ((ApplicationContextAware) listener).setApplicationContext(context);
  36. }
  37. context.addApplicationListener(listener);
  38. }
  39. this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
  40. }
  41. @Override
  42. public void started(ConfigurableApplicationContext context) {
  43. context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
  44. }
  45. @Override
  46. public void running(ConfigurableApplicationContext context) {
  47. context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
  48. }
  49. @Override
  50. public void failed(ConfigurableApplicationContext context, Throwable exception) {
  51. ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
  52. if (context != null && context.isActive()) {
  53. // Listeners have been registered to the application context so we should
  54. // use it at this point if we can
  55. context.publishEvent(event);
  56. }
  57. else {
  58. // An inactive context may not have a multicaster so we use our multicaster to
  59. // call all of the context's listeners instead
  60. if (context instanceof AbstractApplicationContext) {
  61. for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
  62. .getApplicationListeners()) {
  63. this.initialMulticaster.addApplicationListener(listener);
  64. }
  65. }
  66. this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
  67. this.initialMulticaster.multicastEvent(event);
  68. }
  69. }
  70. private static class LoggingErrorHandler implements ErrorHandler {
  71. private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);
  72. @Override
  73. public void handleError(Throwable throwable) {
  74. logger.warn("Error calling ApplicationEventListener", throwable);
  75. }
  76. }
  77. }

调用了 contextLoaded(),在 RestartListener 的 onApplicationEvent() 中进行调用

RestartListener

  1. public class RestartListener implements SmartApplicationListener {
  2. private ConfigurableApplicationContext context;
  3. private ApplicationPreparedEvent event;
  4. @Override
  5. public int getOrder() {
  6. return 0;
  7. }
  8. @Override
  9. public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
  10. return ApplicationPreparedEvent.class.isAssignableFrom(eventType)
  11. || ContextRefreshedEvent.class.isAssignableFrom(eventType)
  12. || ContextClosedEvent.class.isAssignableFrom(eventType);
  13. }
  14. @Override
  15. public boolean supportsSourceType(Class<?> sourceType) {
  16. return true;
  17. }
  18. @Override
  19. public void onApplicationEvent(ApplicationEvent input) {
  20. if (input instanceof ApplicationPreparedEvent) {
  21. this.event = (ApplicationPreparedEvent) input;
  22. if (this.context == null) {
  23. this.context = this.event.getApplicationContext();
  24. }
  25. }
  26. else if (input instanceof ContextRefreshedEvent) {
  27. if (this.context != null && input.getSource().equals(this.context)
  28. && this.event != null) {
  29. this.context.publishEvent(this.event);
  30. }
  31. }
  32. else {
  33. if (this.context != null && input.getSource().equals(this.context)) {
  34. this.context = null;
  35. this.event = null;
  36. }
  37. }
  38. }
  39. }

RestartListener 的 onApplicationEvent() 传入 ApplicationPreparedEvent,调用 AbstractApplicationContext 的 refresh(),即进行 ioc 容器重启,此时是第一次

调用 EventPublishingRunListener 的 running(),进行新的配置加载

调用 PropertySourceBootstrapConfiguration 的 initialize(),间接调用 NacosPropertySourceLocator 的 locate() 进行文件加载

调用 RestartListener 的 onApplicationEvent(),参数为 ApplicationPreparedEvent,调用 AbstractApplicationContext 的 refresh() 进行 ioc 容器重启,此时是第二次

调用 RestartListener 的 onApplicationEvent(),参数为 ContextRefreshedEvent

至此,ContextRefresher 的 refreshEnvironment() 逻辑执行完毕

接下来调用 RefreshScope 的 refreshAll(),间接调用父类 GenericScope 的 destroy(),发布事件 RefreshScopeRefreshedEvent 到 ApplicationContext 中

java 连接 nacos 后会定时心跳连接

通过 NacosWatch 开启 ThreadPoolTaskScheduler 进行定时任务发起,事件为 HeartbeatEvent。

总结

nacos 的在页面上的配置信息的更新是通过 jvm 重启实现的。想到了 jvm 启动后,无法做热更新,这么做也是不错的选择。由于做了重启,这个适合在没有访问的情况下执行,如果在操作过程中有事务在执行会不好,但是在生产环境中是否有这样的应用目前还不清楚。

由此可以看到,spring 中大量使用了事件,联想到了观察者模式、消息队列、消息通知、web 请求响应、窗口点击事件等。

版本变化

从 spring boot 2.4.x 开始,代码逻辑发生了变化,如下

新加了注解 @ConditionalOnBootstrapEnabled 和 @ConditionalOnBootstrapDisabled 用于自动刷新处理。

新加 LegacyContextRefresher

将 ContextRefresher 修改为抽象类,添加抽象方法 updateEnvironment(),让子类继承并重写。
addConfigFilesToEnvironment() 的相关逻辑迁移到 LegacyContextRefresher。

将 ConfigFileApplicationListener 替换为 BootstrapConfigFileApplicationListener
重写 ConfigFileApplicationListener 的 onApplicationEvent() 禁用其中的异常。

参考链接

https://blog.csdn.net/luqiang81191293/article/details/106678065

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/138980
推荐阅读
相关标签
  

闽ICP备14008679号