@RefreshScope那些事

要说清楚RefreshScope,先要了解Scope

  • Scope(org.springframework.beans.factory.config.Scope)是Spring 2.0开始就有的核心的概念
  • RefreshScope(org.springframework.cloud.context.scope.refresh)是spring cloud提供的一种特殊的scope实现,用来实现配置、实例热加载。
  • Scope -> GenericScope -> RefreshScope

@RefreshScope原理及使用 - 图1

1. Scope与ApplicationContext生命周期

AbstractBeanFactory#doGetBean创建Bean实例

  1. protected <T> T doGetBean(...){
  2. final RootBeanDefinition mbd = ...
  3. if (mbd.isSingleton()) {
  4. ...
  5. } else if (mbd.isPrototype())
  6. ...
  7. } else {
  8. String scopeName = mbd.getScope();
  9. final Scope scope = this.scopes.get(scopeName);
  10. Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {...});
  11. ...
  12. }
  13. ...
  14. }

Singleton和Prototype是硬编码的,并不是Scope子类。 Scope实际上是自定义扩展的接口

Scope Bean实例交由Scope自己创建,例如SessionScope是从Session中获取实例的,ThreadScope是从ThreadLocal中获取的,而RefreshScope是在内建缓存中获取的

2. @Scope 对象的实例化

@RefreshScope 是scopeName=”refresh”的 @Scope

  1. ...
  2. @Scope("refresh")
  3. public @interface RefreshScope {
  4. ...
  5. }

@Scope 的注册 AnnotatedBeanDefinitionReader#registerBean

  1. public void registerBean(...){
  2. ...
  3. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
  4. abd.setScope(scopeMetadata.getScopeName());
  5. ...
  6. definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  7. }

读取@Scope元数据, AnnotationScopeMetadataResolver#resolveScopeMetadata

  1. public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
  2. AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
  3. annDef.getMetadata(), Scope.class);
  4. if (attributes != null) {
  5. metadata.setScopeName(attributes.getString("value"));
  6. ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
  7. if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
  8. proxyMode = this.defaultProxyMode;
  9. }
  10. metadata.setScopedProxyMode(proxyMode);
  11. }
  12. }

Scope实例对象通过ScopedProxyFactoryBean创建,其中通过AOP使其实现ScopedObject接口,这里不再展开

现在来说说RefreshScope是如何实现配置和实例刷新的

RefreshScope注册

RefreshAutoConfiguration#RefreshScopeConfiguration

  1. @Component
  2. @ConditionalOnMissingBean(RefreshScope.class)
  3. protected static class RefreshScopeConfiguration implements BeanDefinitionRegistryPostProcessor{
  4. ...
  5. registry.registerBeanDefinition("refreshScope",
  6. BeanDefinitionBuilder.genericBeanDefinition(RefreshScope.class)
  7. .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
  8. .getBeanDefinition());
  9. ...
  10. }

RefreshScope extends GenericScope, 大部分逻辑在 GenericScope 中

GenericScope#postProcessBeanFactory 中向AbstractBeanFactory注册自己

  1. public class GenericScope implements Scope, BeanFactoryPostProcessor...{
  2. @Override
  3. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
  4. throws BeansException {
  5. beanFactory.registerScope(this.name/*refresh*/, this/*RefreshScope*/);
  6. ...
  7. }
  8. }

3. RefreshScope 刷新过程

入口在ContextRefresher#refresh

  1. refresh() {
  2. Map<String, Object> before = extract(
  3. this.context.getEnvironment().getPropertySources());
  4. addConfigFilesToEnvironment();
  5. Set<String> keys = changes(before,
  6. extract(this.context.getEnvironment().getPropertySources())).keySet();
  7. this.context.⑤publishEvent(new EnvironmentChangeEvent(keys));
  8. this.scope.⑥refreshAll();
  9. }

①提取标准参数(SYSTEM,JNDI,SERVLET)之外所有参数变量
②把原来的Environment里的参数放到一个新建的Spring Context容器下重新加载,完事之后关闭新容器
③提起更新过的参数(排除标准参数)
④比较出变更项
⑤发布环境变更事件,接收:EnvironmentChangeListener/LoggingRebinder
⑥RefreshScope用新的环境参数重新生成Bean
重新生成的过程很简单,清除refreshscope缓存幷销毁Bean,下次就会重新从BeanFactory获取一个新的实例(该实例使用新的配置)

RefreshScope#refreshAll

  1. public void refreshAll() {
  2. <b>super.destroy();</b>
  3. this.context.publishEvent(new RefreshScopeRefreshedEvent());
  4. }
  5. GenericScope#destroy
  6. public void destroy() {
  7. ...
  8. Collection<BeanLifecycleWrapper> wrappers = <b>this.cache.clear()</b>;
  9. for (BeanLifecycleWrapper wrapper : wrappers) {
  10. <b>wrapper.destroy();</b>
  11. }
  12. }

4. Spring Cloud Bus 如何触发 Refresh

BusAutoConfiguration#BusRefreshConfiguration 发布一个RefreshBusEndpoint

  1. @Configuration
  2. @ConditionalOnClass({ Endpoint.class, RefreshScope.class })
  3. protected static class BusRefreshConfiguration {
  4. @Configuration
  5. @ConditionalOnBean(ContextRefresher.class)
  6. @ConditionalOnProperty(value = "endpoints.spring.cloud.bus.refresh.enabled", matchIfMissing = true)
  7. protected static class BusRefreshEndpointConfiguration {
  8. @Bean
  9. public RefreshBusEndpoint refreshBusEndpoint(ApplicationContext context,
  10. BusProperties bus) {
  11. return new RefreshBusEndpoint(context, bus.getId());
  12. }
  13. }
  14. }

RefreshBusEndpoint 会从http端口触发广播RefreshRemoteApplicationEvent事件

  1. @Endpoint(id = "bus-refresh")
  2. public class RefreshBusEndpoint extends AbstractBusEndpoint {
  3. public void busRefresh() {
  4. publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), null));
  5. }
  6. }

BusAutoConfiguration#refreshListener 负责接收事件(所有配置bus的节点)

  1. @Bean
  2. @ConditionalOnProperty(value = "spring.cloud.bus.refresh.enabled", matchIfMissing = true)
  3. @ConditionalOnBean(ContextRefresher.class)
  4. public RefreshListener refreshListener(ContextRefresher contextRefresher) {
  5. return new RefreshListener(contextRefresher);
  6. }

RefreshListener#onApplicationEvent 触发 ContextRefresher

  1. public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
  2. Set<String> keys = contextRefresher.refresh();
  3. }

大部分需要更新的服务需要打上@RefreshScope, EurekaClient是如何配置更新的

EurekaClientAutoConfiguration#RefreshableEurekaClientConfiguration

  1. @Configuration
  2. @ConditionalOnRefreshScope
  3. protected static class RefreshableEurekaClientConfiguration{
  4. @Bean
  5. @RefreshScope
  6. public EurekaClient eurekaClient(...) {
  7. return new CloudEurekaClient(manager, config, this.optionalArgs,
  8. this.context);
  9. }
  10. @Bean
  11. @RefreshScope
  12. public ApplicationInfoManager eurekaApplicationInfoManager(...) {
  13. ...
  14. return new ApplicationInfoManager(config, instanceInfo);
  15. }
  16. }

5. Nacos结合SpringCloud动态刷新原理

  1. 监听Nacos中配置更新

企业微信截图_16382765697404.png
2.监听到变更的key,发送 EnvironmentChangeEvent 变更
企业微信截图_16382770527590.png

  1. 发送事件通知后,updateEnviroment()方法会最终调用NacosProetySourceLocator,更新Environment中的Properties的值,在发送EnviromentChangeEvent, 去Rebind对应的Bean,达到重新刷新的目的

企业微信截图_16383575223776.png
企业微信截图_16383658072536.png

6. Dubbo自带配置中心原理

  1. 自行向Nacos注册了一些Dubbo自己需要监听的配置企业微信截图_16382773472521.png
  1. Dubbo自带配置中心启动时配置覆盖原理

企业微信截图_16382841325145.png