Spring Cloud @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

Scope與ApplicationContext生命周期

AbstractBeanFactory#doGetBean創建Bean實例

protected <T> T doGetBean(...){
    final RootBeanDefinition mbd = ...
    if (mbd.isSingleton()) {
        ...
    } else if (mbd.isPrototype())
       ...
    } else {
          String scopeName = mbd.getScope();
          final Scope scope = this.scopes.get(scopeName);
          Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {...});
          ...
    }
    ...
}

Singleton和Prototype是硬編碼的,並不是Scope子類。

Scope實際上是自定義擴展的接口,Scope Bean實例交由Scope自己創建,例如SessionScope是從Session中獲取實例的,ThreadScope是從ThreadLocal中獲取的,而RefreshScope是在內建緩存中獲取的。

@Scope 對象的實例化

@RefreshScope 是scopeName="refresh"的 @Scope

...
@Scope("refresh")
public @interface RefreshScope {
  ...
}

@Scope 的注冊 AnnotatedBeanDefinitionReader#registerBean

public void registerBean(...){
...
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
  abd.setScope(scopeMetadata.getScopeName());
  ...
  definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
}

讀取@Scope元數據, AnnotationScopeMetadataResolver#resolveScopeMetadata

public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
  AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
  annDef.getMetadata(), Scope.class);
  if (attributes != null) {
      metadata.setScopeName(attributes.getString("value"));
      ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
      if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
          proxyMode = this.defaultProxyMode;
      }
      metadata.setScopedProxyMode(proxyMode);
  }
}

Scope實例對象通過ScopedProxyFactoryBean創建,其中通過AOP使其實現ScopedObject接口,這里不再展開。

說RefreshScope是如何實現配置和實例刷新的

RefreshScope注冊

RefreshAutoConfiguration#RefreshScopeConfiguration

@Component
@ConditionalOnMissingBean(RefreshScope.class)
protected static class RefreshScopeConfiguration implements BeanDefinitionRegistryPostProcessor{
...
  registry.registerBeanDefinition("refreshScope",
  BeanDefinitionBuilder.genericBeanDefinition(RefreshScope.class)
                      .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
                      .getBeanDefinition());
...
}

RefreshScope extends GenericScope, 大部分邏輯在 GenericScope 中。

GenericScope#postProcessBeanFactory 中向AbstractBeanFactory注冊自己

public class GenericScope implements Scope, BeanFactoryPostProcessor...{
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
      throws BeansException {
      beanFactory.registerScope(this.name/*refresh*/, this/*RefreshScope*/);
      ...
  }
}

RefreshScope 刷新過程

入口在ContextRefresher#refresh

refresh() {
  Map<String, Object> before = ①extract(
          this.context.getEnvironment().getPropertySources());
  ②addConfigFilesToEnvironment();
  Set<String> keys = ④changes(before,
          ③extract(this.context.getEnvironment().getPropertySources())).keySet();
  this.context.⑤publishEvent(new EnvironmentChangeEvent(keys));
  this.scope.⑥refreshAll();
 }

①提取標准參數(SYSTEM,JNDI,SERVLET)之外所有參數變量

②把原來的Environment里的參數放到一個新建的Spring Context容器下重新加載,完事之后關閉新容器

③提起更新過的參數(排除標准參數)

④比較出變更項

⑤發布環境變更事件,接收:EnvironmentChangeListener/LoggingRebinder

⑥RefreshScope用新的環境參數重新生成Bean,重新生成的過程很簡單,清除refreshscope緩存幷銷毀Bean,下次就會重新從BeanFactory獲取一個新的實例(該實例使用新的配置)

RefreshScope#refreshAll

public void refreshAll() {
      <b>super.destroy();</b>
      this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

GenericScope#destroy

public void destroy() {
  ...
  Collection<BeanLifecycleWrapper> wrappers = <b>this.cache.clear()</b>;
  for (BeanLifecycleWrapper wrapper : wrappers) {
      <b>wrapper.destroy();</b>
  }
}

Spring Cloud Bus 如何觸發 Refresh

BusAutoConfiguration#BusRefreshConfiguration 發布一個RefreshBusEndpoint

@Configuration
@ConditionalOnClass({ Endpoint.class, RefreshScope.class })
protected static class BusRefreshConfiguration {

  @Configuration
  @ConditionalOnBean(ContextRefresher.class)
  @ConditionalOnProperty(value = "endpoints.spring.cloud.bus.refresh.enabled", matchIfMissing = true)
  protected static class BusRefreshEndpointConfiguration {
      @Bean
      public RefreshBusEndpoint refreshBusEndpoint(ApplicationContext context,
              BusProperties bus) {
          return new RefreshBusEndpoint(context, bus.getId());
      }
  }
}

RefreshBusEndpoint 會從http端口觸發廣播RefreshRemoteApplicationEvent事件

@Endpoint(id = "bus-refresh")
public class RefreshBusEndpoint extends AbstractBusEndpoint {
   public void busRefresh() {
      publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), null));
  }
}

BusAutoConfiguration#refreshListener 負責接收事件(所有配置bus的節點)

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

RefreshListener#onApplicationEvent 觸發 ContextRefresher

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

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

EurekaClientAutoConfiguration#RefreshableEurekaClientConfiguration

@Configuration
@ConditionalOnRefreshScope
protected static class RefreshableEurekaClientConfiguration{
  @Bean
  @RefreshScope
  public EurekaClient eurekaClient(...) {
      return new CloudEurekaClient(manager, config, this.optionalArgs,
              this.context);
  }
  
  @Bean
  @RefreshScope
  public ApplicationInfoManager eurekaApplicationInfoManager(...) {
      ...
      return new ApplicationInfoManager(config, instanceInfo);
  }
  
}

作者:黃大海
https://www.jianshu.com/p/188013dd3d02


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM