天天看點

Spring Cloud @RefreshScope 的原理分析:掃描 Bean 定義

作者:晾幹的紅領巾

背景

最近讀了一下 spring cloud 的 @RefreshScope 生效的源碼,總結一下該注解的 refresh 類型的類執行個體化的過程。

關鍵技術點:

  1. 掃描過程中對 @RefreshScope 注解做了特殊處理,會額外注冊兩個BeanDefinition。
  2. GenericScope 實作了 BeanDefinitionRegistryPostProcessor 接口,并對 refresh 的 BeanDefinition 添加了構造函數參數為自己,同時設定 beanClass 屬性為 GenericScope.LockedScopedProxyFactoryBean.class,合成屬性為 true。
  3. GenericScope.LockedScopedProxyFactoryBean 類實作了 FactoryBean、BeanFactoryAware、MethodInterceptor 接口。

拆解三個部分:掃描 Bean 定義、代理類執行個體化、代理類方法調用,從這三個過程的源碼,來了解為什麼 @RefreshScope 标注的類的實能夠動态應對環境變量的變化。

原始掃描 Bean 定義過程

ClassPathBeanDefinitionScanner 掃描時會對 @RefreshScope 注解的類的 BeanDefinition 定義資訊作兩個操作:

Spring Cloud @RefreshScope 的原理分析:掃描 Bean 定義

這個 applyScopedProxyMode 方法判斷目前代理模式,如果

Spring Cloud @RefreshScope 的原理分析:掃描 Bean 定義

而 @RefreshScope 的這個屬性是 ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS ,即針對這類注解的類在掃描時,會額外建立代理類的 BeanDefinition :

Spring Cloud @RefreshScope 的原理分析:掃描 Bean 定義

主要是三個操作:

  1. 以原始的 BeanDefinition 建立一個代理類的 RootBeanDefinition,并且設定它的 decoratedDefinition 為原始對象,代理類的類型為 ScopedProxyFactoryBean ;
  2. 再注冊一個以 "scopedTarget." + originalBeanName ,基于原始配置的目标定義,即真正實體的定義修改了;
  3. 最後再注冊一個用 originalBeanName 的代理類的定義,就是偷梁換柱,完全代理了目标對象。
  4. 原始類定義的 autowireCandidate 為 false ,primary 也為 false ,那麼就能保證明力擷取時優先找到的候選對象是代理對象。

GenericScope 增強注冊

GenericScope 實作了 BeanDefinitionRegistryPostProcessor 接口,而且它 Order 優先級較低,保證在 第一步的掃描之後執行如下操作:

Spring Cloud @RefreshScope 的原理分析:掃描 Bean 定義

這裡對掃描階段建立的代理類的 BeanDefinition 作了三個增強:

  1. 修改代理的 BeanDefinition 類型為自己的 LockedScopedProxyFactoryBean ;
  2. 為代理類的 BeanDefinition 添加一個構造函數的參數值為自己,因為 LockedScopedProxyFactoryBean 類的構造函數需要一個入參 Scope;
  3. 設定合成屬性為真。

啟示錄

@RefreshScope 是 spring cloud 的技術,它本質上擴充了 spring 架構的 Scope,跟其他單例、原型、請求類型、會話類型一樣,是一種擴充 Scope ,巧妙應用了 ScopedProxyMode.TARGET_CLASS 和 AOP ,把 refresh 類型的對象托管起來,保證環境變量變更時,銷毀舊的執行個體、擷取最新的執行個體。

下一篇講繼續總結 ScopedProxyFactoryBean 這個類是如何建立出 @RefreshScope 注解的執行個體的,為什麼能将一個委托類型偷梁換柱為一個 JdkDynamicAopProxy 代理類。