springcloud ConfigDataContextRefresher刷新central config

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

配置bootstrap.yml

management:
  endpoint:
    refresh:
      enabled: true #默认true
  endpoints:
    web:
      exposure:
        include: '*' #默认情况下只对外暴露了health和info端点

spring:
  application:
    name: ms-client-a
  cloud:
    config:
#      discovery:
#        enabled: true
#        service-id: ms-config-server
      uri: http://localhost:8080/
      profile: dev
      label: master

编码

在需要动态刷新的类上添加@RefreshScope注解并通过@value注解引用配置文件中的配置

@RestController
@RequestMapping("/adviser/loss")
@RefreshScope
public class AdviserLossController {

    @Value("${custom.name}")
    private String name;

    @Value("${custom.age}")
    private Integer age;

 @GetMapping("/test")
    public String test(){
        return name + ":" + age;
    }
}

配置git远程文件

custom:
  name: 张三
  age: 20

启动项目

首次启动时,config client会请求config server加载远程配置

测试

首次请求/adviser/loss/test接口,会发现返回=》张三:18,然后修改git上的配置,将年龄修改为20,再次请求/adviser/loss/test接口,发现配置并未更新。那么此时就需要我们手动刷新配置了:发送http post /actuator/refresh接口,此时再次请求/adviser/loss/test接口,会发现返回=》张三:20,说明配置被成功刷新。

源码分析

@RefreshScope

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {

 /**
  * @see Scope#proxyMode()
  * @return proxy mode
  */
 ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

该注解上添加了@Scope("refresh")注解指明了作用域名为refresh

RefreshScope

public class RefreshScope extends GenericScope implements ApplicationContextAware,
  ApplicationListener<ContextRefreshedEvent>, Ordered {
  // 省略代码。。。。。。
}

该类继承自GenericScope,其核心方法都在父类GenericScope中,而GenericScope继承BeanFactoryPostProcessor

GenericScope

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
  throws BeansException {
 this.beanFactory = beanFactory;
 // 放入到缓存中
 beanFactory.registerScope(this.name, this);
 setSerializationId(beanFactory);
}

注册scope,名字为refresh,值为RefreshScope实例。

添加了@RefreshScope注解的Bean对象会被@ComponentScan注解扫描到,核心代码在ClassPathBeanDefinitionScanner类中的doScan方法中:

ClassPathBeanDefinitionScanner

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
 Assert.notEmpty(basePackages, "At least one base package must be specified");
 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
 for (String basePackage : basePackages) {
  Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  for (BeanDefinition candidate : candidates) {
   // 关键代码
   ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
   candidate.setScope(scopeMetadata.getScopeName());
   String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
   if (candidate instanceof AbstractBeanDefinition) {
    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
   }
   if (candidate instanceof AnnotatedBeanDefinition) {
    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
   }
   if (checkCandidate(beanName, candidate)) {
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    definitionHolder =
      AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    beanDefinitions.add(definitionHolder);
    registerBeanDefinition(definitionHolder, this.registry);
   }
  }
 }
 return beanDefinitions;
}

核心代码在this.scopeMetadataResolver.resolveScopeMetadata(candidate)这一行,这里就不展开讲了,希望同学自己去扩展阅读相关源码。

注册的Scope将会在AbstractBeanFactory#doGetBean方法中调用,该方法中会先拿到当前BeanDefinition中定义的Scope,通过scopeNameMap集合中拿到Scope类,最后调用Scopeget方法获取实例对象。

AbstractBeanFactory

protected <T> T doGetBean(
   String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
   throws BeansException {
 // 省略部分代码
 String scopeName = mbd.getScope();
 if (!StringUtils.hasLength(scopeName)) {
  throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
 }
 // 1.从缓存中取,在beanFactory的registerScope方法调用时放入缓存
 Scope scope = this.scopes.get(scopeName);
 if (scope == null) {
  throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
 }
 try {
  // 2.调用scope的get方法创建bean,通过匿名内部类创建ObjectFactory对象并实现getObject方法。在GenericScope的get方法会回调此处的实现,创建一个bean。
  Object scopedInstance = scope.get(beanName, () -> {
   beforePrototypeCreation(beanName);
   try {
    return createBean(beanName, mbd, args);
   }
   finally {
    afterPrototypeCreation(beanName);
   }
  });
  beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
 }
 catch (IllegalStateException ex) {
  throw new ScopeNotActiveException(beanName, scopeName, ex);
 }

 // 省略部分代码
}

在2处,最终会调用RefreshScope#get方法:

GenericScope

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
 BeanLifecycleWrapper value = this.cache.put(name,
   new BeanLifecycleWrapper(name, objectFactory));
 this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
 try {
  return value.getBean();
 }
 catch (RuntimeException e) {
  this.errors.put(name, e);
  throw e;
 }
}

先是将ObjectFactory实例对象包装成BeanLifecycleWrapper放入缓存中(缓存中没有则放入,有则返回原对象),然后调用BeanLifecycleWrappergetBean方法:

GenericScope@BeanLifecycleWrapper

public Object getBean() {
 if (this.bean == null) {
  synchronized (this.name) {
   if (this.bean == null) {
    this.bean = this.objectFactory.getObject();
   }
  }
 }
 return this.bean;
}

如果bean对象等于null,则通过this.objectFactory.getObject(),又回到AbstractBeanFactorydoGetBean方法中,创建一个bean并返回。

至此已经分析完了RefreshScope类和@RefreshScope注解的来龙去脉,下面分析下Refresh端点触发时机。

Refresh端点触发时机

当调用/actuator/refresh端点时,执行如下refresh:

@Endpoint(id = "refresh")
public class RefreshEndpoint {

 private ContextRefresher contextRefresher;

 public RefreshEndpoint(ContextRefresher contextRefresher) {
  this.contextRefresher = contextRefresher;
 }

 @WriteOperation
 public Collection<String> refresh() {
  Set<String> keys = this.contextRefresher.refresh();
  return keys;
 }

}

RefreshEndpoint是在RefreshEndpointAutoConfiguration类中创建并配置的。内部会通过调用ContextRefresher#refresh方法:

public synchronized Set<String> refresh() {
 //1.刷新环境
 Set<String> keys = refreshEnvironment();
 //2.清空缓存
 this.scope.refreshAll();
 return keys;
}

public synchronized Set<String> refreshEnvironment() {
 //1:提取配置信息修改之前的值,排除systemEnvironment、systemProperties、jndiProperties、servletConfigInitParams、servletContextInitParams、configurationProperties相关配置  
 Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
 //2:重新加载读取配置信息,调用内部的抽象方法,由具体的子类实现
 updateEnvironment();
 //3:获取所有改变的配置
 Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
 //4:发布EnvironmentChangeEvent事件
 this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
 return keys;
}

在2处,重新加载配置,在当前的版本,有2中实现方式:一种是兼容旧版本的实现,一种是新版本的实现,这两种实现分别在RefreshAutoConfiguration中配置:

RefreshAutoConfiguration

//兼容旧版本的实现
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapEnabled
public LegacyContextRefresher legacyContextRefresher(ConfigurableApplicationContext context, RefreshScope scope,
  RefreshProperties properties) {
 return new LegacyContextRefresher(context, scope, properties);
}

//新版本的实现
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapDisabled
public ConfigDataContextRefresher configDataContextRefresher(ConfigurableApplicationContext context,
  RefreshScope scope, RefreshProperties properties) {
 return new ConfigDataContextRefresher(context, scope, properties);
}

旧版本的实现其原理是:内部启动一个非web环境的SpringBoot应用,重新读取配置信息,用新配置替换旧配置,这个很容易理解。 新版本的实现方式,我也跟踪了源码,也在网上找了很多资料,但是目前网上的都是基于旧版本的分析。关于新版本的实现属实不太理解(功力尚浅),我个人也在学习和总结,后续如果有新的进展,我也会及时更新的,如果您有相关资料或见解,也烦请指教。

在4处发布一个EnvironmentChangeEvent事件,查看该事件的监听器:

ConfigurationPropertiesRebinder

@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
 if (this.applicationContext.equals(event.getSource())
   // Backwards compatible
   || event.getKeys().equals(event.getSource())) {
  rebind();
 }
}

@ManagedOperation
public void rebind() {
 this.errors.clear();
 //遍历所有的配置类(带有@ConfigurationProperties注解的类)
 for (String name : this.beans.getBeanNames()) {
  //对每一个bean进行重新绑定  
  rebind(name);
 }
}

@ManagedOperation
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
    }
//销毁当前的bean     this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
//初始化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;
}

补充

ConfigurationPropertiesRebinder类中的beans是通过构造函数传过来的,接下来先查看这个对象是如何被构造的:

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
public class ConfigurationPropertiesRebinderAutoConfiguration
  implements ApplicationContextAware, SmartInitializingSingleton {

 private ApplicationContext context;

 @Override
 public void setApplicationContext(ApplicationContext applicationContext) {
  this.context = applicationContext;
 }

 @Bean
 @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
 public static ConfigurationPropertiesBeans configurationPropertiesBeans() {
  return new ConfigurationPropertiesBeans();
 }

 @Bean
 @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
 public ConfigurationPropertiesRebinder configurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
  ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(beans);
  return rebinder;
 }

 @Override
 public void afterSingletonsInstantiated() {
  // After all beans are initialized explicitly rebind beans from the parent
  // so that changes during the initialization of the current context are
  // reflected. In particular this can be important when low level services like
  // decryption are bootstrapped in the parent, but need to change their
  // configuration before the child context is processed.
  if (this.context.getParent() != null) {
   // TODO: make this optional? (E.g. when creating child contexts that prefer to
   // be isolated.)
   ConfigurationPropertiesRebinder rebinder = this.context.getBean(ConfigurationPropertiesRebinder.class);
   for (String name : this.context.getParent().getBeanDefinitionNames()) {
    rebinder.rebind(name);
   }
  }
 }

}

在这个自动配置类中创建了ConfigurationPropertiesRebinder并且将ConfigurationPropertiesBeans注入。ConfigurationPropertiesBeans是个BeanPostProcessor处理器:

@Component
public class ConfigurationPropertiesBeans implements BeanPostProcessor, ApplicationContextAware {
 //省略部分代码...
 private Map<String, ConfigurationPropertiesBean> beans = new HashMap<>();
 
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  if (isRefreshScoped(beanName)) {
   return bean;
  }
  ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.get(this.applicationContext, bean,
    beanName);
  if (propertiesBean != null) {
   //添加有@ConfigurationProperties注解的bean都将保存在该集合中
   this.beans.put(beanName, propertiesBean);
  }
  return bean;
 }

 private boolean isRefreshScoped(String beanName) {
   if (this.refreshScope == null && !this.refreshScopeInitialized) {
    this.refreshScopeInitialized = true;
    for (String scope : this.beanFactory.getRegisteredScopeNames()) {
     if (this.beanFactory.getRegisteredScope(
       scope) instanceof org.springframework.cloud.context.scope.refresh.RefreshScope) {
      this.refreshScope = scope;
      break;
     }
    }
   }
   if (beanName == null || this.refreshScope == null) {
    return false;
   }
   return this.beanFactory.containsBeanDefinition(beanName)
     && this.refreshScope.equals(this.beanFactory.getBeanDefinition(beanName).getScope());
  }

 //省略部分代码...
}

BeanPostProcessorpostProcessBeforeInitialization方法执行,在初始化bean的时候执行,在这里就会判断当前的Bean是否是RefreshScope Bean。

isRefreshScoped方法中遍历注册的所有Scope并且判断是否是有RefreshScope,先从注册的所有Scope中查找RefreshScope,如果不是返回false,如果是则返回true。如果isRefreshScoped方法返回的false就判断当前Bean是否有@ConfigurationProperties注解如果有会被包装成ConfigurationPropertiesBean存入当前的beans集合中(当有refresh发生时会重新绑定这些bean)。接下来继续进入到上面的ConfigurationPropertiesRebinder#rebind方法中。

RefreshScope刷新处理

回到ContextRefresherrefresh方法:

public synchronized Set<String> refresh() {
 Set<String> keys = refreshEnvironment();
 //清空缓存
 this.scope.refreshAll();
 return keys;
}

RefreshScope#refreshAll

@ManagedOperation(description = "Dispose of the current instance of all beans "
   + "in this scope and force a refresh on next method execution.")
public void refreshAll() {
 //调用父类GenericScope的destroy方法
 super.destroy();
 //发布RefreshScopeRefreshedEvent事件,我们可以写个监听程序监听该事件
 this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

GenericScope

@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();
}

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;
 }
}

当前清空了缓存对象后,下次再进入注入的时候会再次调用ObjectFacotry#getObject方法创建新的对象。

总结

当触发了refresh后,所有的带有@ConfigurationProperties注解的Bean都会自动的刷新并不需要@RefreshScope注解。而有@RefreshScope注解的一般在应用在非配置类上,有成员属性使用@Value注解的,如下:

@RestController
@RequestMapping("/refreshBeanProp")
@RefreshScope
public class RefreshScopeBeanPropController {
    
  @Value("${custom}")
  private String custom ;
    
  @GetMapping("/get")
  public String get() {
    return custom ;
  }
    
}

在此种情况下,调用/actuator/refresh 可使custom动态刷新,在ContextRefresher#refresh中将缓存的Bean清空了后重新生成Bean。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要搭建Spring Cloud开发环境,你需要进行以下步骤: 1. 首先,你需要确保你的Java开发环境已经安装和配置好了。你可以去Oracle官网下载和安装Java Development Kit (JDK)。 2. 接下来,你需要使用构建工具(比如Maven或Gradle)来构建你的项目。在你的项目的pom.xml(如果使用Maven)或build.gradle(如果使用Gradle)文件中,你需要添加Spring Cloud的相关依赖。你可以在Maven Central Repository搜索并添加Spring Cloud的依赖,其中包括中提到的`org.springframework.cloud:spring-cloud-config-server`和中提到的`org.springframework.cloud:spring-cloud-config-client`。 3. 在你的项目中,你需要创建一个Spring Cloud Config Server。这是一个服务,用于管理和提供配置信息给其他应用程序。你可以使用Spring Boot创建一个新的Spring Cloud Config Server应用程序,并在其配置文件中指定一些基本设置,比如配置文件存储库的位置和访问控制规则。 4. 接下来,你需要创建一个Spring Cloud Config Client应用程序。这是真正的应用程序,它将从Config Server获取配置信息并使用它们。你可以使用Spring Boot创建一个新的Spring Cloud Config Client应用程序,并在其配置文件中指定Config Server的URL以及应用程序需要的其他配置属性。 5. 最后,你可以根据需要创建其他的Spring Cloud组件,比如服务注册与发现(如Spring Cloud Netflix Eureka),负载均衡(如Spring Cloud Netflix Ribbon)等等。根据你的具体需求,你可以在你的项目中添加这些组件。 综上所述,搭建Spring Cloud开发环境的步骤包括安装Java开发环境、配置构建工具、添加Spring Cloud依赖、创建Config ServerConfig Client应用程序,以及根据需要添加其他的Spring Cloud组件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值