Spring與Dubbo整合原理

Spring與Dubbo整合原理

應用啓動類與配置

public class Application {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
        context.start();
        System.in.read();
    }

    @Configuration
    @EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
    @PropertySource("classpath:/spring/dubbo-provider.properties")
    static class ProviderConfiguration {
       
    }
}

應用配置類爲ProviderConfiguration, 在配置上有兩個比較重要的註解

  1. @PropertySource表示將dubbo-provider.properties中的配置項添加到Spring容器中,可以通過@Value的方式獲取到配置項中的值
  2. @EnableDubbo(scanBasePackages = “org.apache.dubbo.demo.provider”)表示對指定包下的類進行掃描,掃描@Service與@Reference註解,並且進行處理

@EnableDubbo
在EnableDubbo註解上,有另外兩個註解,也是研究Dubbo最重要的兩個註解

  1. @EnableDubboConfig
  2. @DubboComponentScan
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {
    boolean multiple() default true;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

}

注意兩個註解中對應的@Import註解所導入的類:

  1. DubboConfigConfigurationRegistrar
  2. DubboComponentScanRegistrar
    Spring在啓動時會解析這兩個註解,並且執行對應的Registrar類中的registerBeanDefinitions方法(這是Spring中提供的擴展功能。)

DubboConfigConfigurationRegistrar
Spring啓動時,會調用DubboConfigConfigurationRegistrar的registerBeanDefinitions方法,會對Properties文件進行解析,主要完成的事情是根
據Properties文件的每個配置項的前綴、參數名、參數值生成對應的Bean。
比如前綴爲"dubbo.application"的配置項,會生成一個ApplicationConfig類型的BeanDefinition。
比如前綴爲"dubbo.protocol"的配置項,會生成一個ProtocolConfig類型的BeanDefinition。
其他前綴對應關係如下:

@EnableDubboConfigBindings({
    @EnableDubboConfigBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.module", type = ModuleConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.config-center", type = ConfigCenterBean.class),
    @EnableDubboConfigBinding(prefix = "dubbo.metadata-report", type = MetadataReportConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.metrics", type = MetricsConfig.class)
})
public static class Single {

}

默認情況下開啓了multiple模式,multiple模式表示開啓多配置模式,意思是這樣的:
如果沒有開啓multiple模式,那麼只支持配置一個dubbo.protocol,比如:

dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.protocol.host=0.0.0.0

如果開啓了multiple模式,那麼可以支持多個dubbo.protocol,比如:

dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20880
dubbo.protocols.p1.host=0.0.0.0

dubbo.protocols.p2.name=http
dubbo.protocols.p2.port=8082
dubbo.protocols.p2.host=0.0.0.0

DubboConfigConfigurationRegistrar的registerBeanDefinitions方法源碼流程:

  1. 根據DubboConfigConfiguration.Single.class的定義來註冊BeanDefinition,如果開啓了multiple模式,則根據DubboConfigConfiguration.Multiple.class的定義來註冊BeanDefinition
  2. 兩者都是調用的registerBeans(BeanDefinitionRegistry registry, Class<?>… annotatedClasses)方法
  3. 在registerBeans方法內,會利用Spring中的AnnotatedBeanDefinitionReader類來加載annotatedClasses參數所指定的類(上面所Single或Multiple類),Spring的AnnotatedBeanDefinitionReader
    類會識別annotatedClasses上的註解,然後開啓解析annotatedClasses類上的註解
  4. 可以發現,不管是Single類,還是Multiple類,類上面定義的註解都是@EnableDubboConfigBindings,所以Spring會解析這個註解,在這個註解的定義上Import了一個DubboConfigBindingsRegistra
    r類,所以這是Spring會去調用DubboConfigBindingsRegistrar類的registerBeanDefinitions方法
  5. 在DubboConfigBindingsRegistrar類的registerBeanDefinitions方法中,會去取EnableDubboConfigBindings註解的value屬性的值,該值是一個數組,數組中存的內容爲@EnableDubboConfigBinding注
    解。此時DubboConfigBindingsRegistrar會去處理各個@EnableDubboConfigBinding註解,使用DubboConfigBindingRegistrar類的registerBeanDefinitions(AnnotationAttributes attributes, Bea
    nDefinitionRegistry registry)去處理各個@EnableDubboConfigBinding註解
  6. attributes表示@EnableDubboConfigBinding註解中的參數對,比如prefix = “dubbo.application”, type = ApplicationConfig.class。
  7. 獲取出對應當前@EnableDubboConfigBinding註解的prefix和AbstractConfig類,ApplicationConfig、RegistryConfig、ProtocolConfig等等都是AbstractConfig類的子類
  8. 從environment.getPropertySources()中獲取對應的prefix的properties,相當於從Properties文件中獲取對應prefix的屬性,後面在生成了AplicationConfig類型的BeanDefinition之後,會把這些屬
    性值賦值給對應的BeanDefinition,但是這裏獲取屬性只是爲了獲取beanName
  9. 在生成Bean之前,需要確定Bean的名字,可以通過在Properties文件中配置相關的id屬性,那麼則對應的值就爲beanName,否則自動生成一個beanName
  10. 對於Multiple模式的配置,會存在多個bean以及多個beanName
  11. 得到beanName之後,向Spring中註冊一個空的BeanDefinition對象,並且向Spring中添加一個DubboConfigBindingBeanPostProcessor(Bean後置處理器),在DubboConfigBindingBeanPostProcessor中
    有一個構造方法,需要傳入prefix和beanName。
  12. 總結一下:一個AbstractConfig的子類會對應一個bean(Multiple模式下會有多個),每個bean對應一個DubboConfigBindingBeanPostProcessor後置處理器。
  13. 至此,Spring掃描邏輯走完了。
  14. 接下來,Spring會根據生成的BeanDefinition生成一個對象,然後會經過DubboConfigBindingBeanPostProcessor後置處理器的處理。
  15. DubboConfigBindingBeanPostProcessor主要用來對其對應的bean進行屬性賦值
  16. 首先通過DubboConfigBinder的默認實現類DefaultDubboConfigBinder,來從Properties文件中獲取prefix對應的屬性值,然後把這些屬性值賦值給AbstractConfig對象中的屬性
  17. 然後看AbstractConfig類中是否存在setName()方法,如果存在則把beanName設置進去
  18. 這樣一個AbstractConfig類的bean就生成好了
  19. 總結一下:Spring在啓動時,會去生成ApplicationConfig、RegistryConfig、ProtocolConfig等等AbstractConfig子類的bean對象,然後從Properties文件中獲取屬性值並賦值到bean對象中去。
    DubboConfigConfigurationRegistrar的邏輯整理完後,就開始整理DubboComponentScanRegistrar的邏輯。

DubboComponentScanRegistrar

DubboConfigConfigurationRegistrar的registerBeanDefinitions方法中:

  1. 首先獲得用戶指定的掃描包路徑
  2. 然後分別生成ServiceAnnotationBeanPostProcessor和ReferenceAnnotationBeanPostProcessor類的BeanDefinition,並註冊到Spring中,注意這兩個類看上去像,但完全不是一個層面的東西。
  3. ServiceAnnotationBeanPostProcessor是一個BeanDefinitionRegistryPostProcessor,是在Spring掃描過程中執行的。
  4. ReferenceAnnotationBeanPostProcessor的父類是AnnotationInjectedBeanPostProcessor,是一個InstantiationAwareBeanPostProcessorAdapter,是在Spring對容器中的bean進行依賴注入時使用的。

ServiceAnnotationBeanPostProcessor

在執行postProcessBeanDefinitionRegistry()方法時,會先生成一個DubboClassPathBeanDefinitionScanner,它負責掃描。接下來的流程:

  1. 從指定的包路徑下掃描@Service註解,掃描得到BeanDefinition
  2. 遍歷每個BeanDefinition
  3. 得到服務實現類、@Service註解信息、服務實現類實現的接口、服務實現類的beanName
  4. 生成一個ServiceBean對應的BeanDefinition,下面稱爲serviceBeanBeanDefinition
  5. 根據@Service註解上的信息對serviceBeanBeanDefinition中的屬性進行賦值
  6. 對ref屬性進行賦值(PropertyReference),賦值爲服務實現類的beanName
  7. 對interface屬性進行賦值(PropertyValue),賦值爲接口名
  8. 對parameters屬性進行賦值,把註解中的parameters屬性值轉化爲map進行賦值
  9. 對methods屬性進行賦值,把註解中的methods屬性值轉化爲List進行賦值
  10. 如果@Service註解中配置了provider屬性,則對provider屬性進行賦值(PropertyReference,表示一個beanName)
  11. monitor、application、module和provider類似
  12. 如果@Service註解中配置了registry屬性,會對registries屬性進行賦值(RuntimeBeanReference)
  13. 如果@Service註解中配置了protocol屬性,會對protocols屬性進行賦值(RuntimeBeanReference)
  14. 到此生成了一個ServiceBean的BeanDefinition
  15. 然後生成一個ServiceBeanName,並把對應的BeanDefinition註冊到Spring中去
  16. 總結一下:ServiceAnnotationBeanPostProcessor主要用來掃描指定包下面的@Service註解,把掃描到的@Service註解所標註的類都生成一個對應的BeanDefinition(會隨着Spring的生命週期生成一
    個對應的Bean),然後遍歷掃描出來的BeanDefinition,根據@Service註解中的參數配置,會生成一個ServiceBean類型的BeanDefinition,並添加到Spring容器中去,所以相當於一個@Service註解會生成
    兩個Bean,一個當前類的Bean,一個ServiceBean類型的Bean。需要注意的是ServiceBean實現了ApplicationListener接口,當Spring啓動完後,會發布ContextRefreshedEvent事件,ServiceBean會處
    理該事件,調用ServiceBean中的export(),該方法就是服務導出的入口。
    注意:關於RuntimeBeanReference參考https://www.yuque.com/renyong-jmovm/ufz328/gbwvk7。

ReferenceAnnotationBeanPostProcessor

ReferenceAnnotationBeanPostProcessor的父類是AnnotationInjectedBeanPostProcessor,是一個InstantiationAwareBeanPostProcessorAdapter,是在Spring對容器中的bean進行依賴注入時使用的。
當Spring根據BeanDefinition生成了實例對象後,就需要對對象中的屬性進行賦值,此時會:

  1. 調用AnnotationInjectedBeanPostProcessor類中的postProcessPropertyValues方法,查找注入點,查找到注入點後就會進行屬性注入,注入點分爲被@Reference註解了的屬性字段,被@Reference註解
    了的方法
  2. 調用AnnotationInjectedBeanPostProcessor類中的findFieldAnnotationMetadata方法查找屬性注入點,返回類型爲List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement>
  3. 調用AnnotationInjectedBeanPostProcessor類中的findAnnotatedMethodMetadata方法查找方法注入點,返回類型爲List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement>
  4. 註解掉找到後,就調用InjectionMetadata的inject方法進行注入。
  5. 針對AnnotatedFieldElement,調用getInjectedObject方法得到注入對象injectedObject,然後通過反射field.set(bean, injectedObject);
  6. 針對AnnotatedMethodElement,調用getInjectedObject方法得到注入對象injectedObject,然後通過反射method.invoke(bean, injectedObject);
  7. 在getInjectedObject方法中,調用doGetInjectedBean方法得到注入對象,doGetInjectedBean方法在ReferenceAnnotationBeanPostProcessor類中提供了實現
  8. 根據@Reference註解中的參數信息與待注入的屬性類型,生成一個serviceBeanName,查看在本應用的Spring容器中是否存在這個名字的bean,如果存在,則表示現在引入的服務就在本地Spring容器中
  9. 根據@Reference註解中的參數信息與待注入的屬性類型,生成一個referenceBeanName
  10. 根據referenceBeanName、@Reference註解中的參數信息與待注入的屬性類型生成一個ReferenceBean對象(注意,這裏直接就是對象了,最終返回的就是這個對象的get方法的返回值)
  11. 把ReferenceBean對象通過beanFactory註冊到Spring中
  12. 那麼ReferenceBean對象是怎麼產生的呢?
  13. AnnotatedInterfaceConfigBeanBuilder類的build方法來生成這個ReferenceBean對象
  14. 先new ReferenceBean();得到一個ReferenceBean實例
  15. 然後調用configureBean(ReferenceBean實例);給ReferenceBean實例的屬性進行賦值
  16. 調用preConfigureBean(attributes, ReferenceBean實例);,把Reference註解的參數值賦值給ReferenceBean實例,除開"application", “module”, “consumer”, “monitor”, "registry"這幾個參數
  17. 調用configureRegistryConfigs對ReferenceBean實例的registries屬性進行賦值,通過@Reference註解中所配置的registry屬性獲得到值,然後根據該值從Spring容器中獲得到bean
  18. 同樣的,通過調用configureMonitorConfig、configureApplicationConfig、configureModuleConfig方法分別進行賦值
  19. 調用postConfigureBean方法對applicationContext、interfaceName、consumer、methods屬性進行賦值
  20. 最後調用ReferenceBean實例的afterPropertiesSet方法
  21. ReferenceBean生成後了之後,就會調用ReferenceBean的get方法得到一個接口的代理對象,最終會把這個代理對象注入到屬性中去
  22. 總結一下:ReferenceAnnotationBeanPostProcessor主要在Spring對容器中的Bean進行屬性注入時進行操作,當Spring對一個Bean進行屬性注入時,先查找@Reference的注入點,然後對注入點進行調
    用,在調用過程中,會根據屬性類型,@Reference註解信息生成一個ReferenceBean,然後對ReferenceBean對象的屬性進行賦值,最後調用ReferenceBean的get方法得到一個代理對象,最終把這個代理
    對象注入給屬性。需要注意的是ReferenceBean的get()方法就是服務引入流程的入口。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章