Dubbo源碼之Spring整合
- 2019 年 12 月 13 日
- 筆記
本文主要介紹了在不同的配置模式下,dubbo與spring整合的原理,即:xml配置、註解配置、自動化配置 三種模式下的配置生效原理。
XML啟動
Schema擴展機制
Spring提供了 Schema 擴展機制,用戶可以自定義 Schema 文件,並自定義 Schema 解析器,然後集成到SpringIOC容器中。 創建自定義擴展,主要有以下步驟:
- 創建 Schema 文件,描述自定義的合法構建模組,也就是xsd文件,主要用於定義數據約束;
- 自定義個處理器類,並實現NamespaceHandler介面,在裡面註冊各個標籤對應的BeanDefinitionParser;
- 自定義一個或多個解析器,實現 BeanDefinitionParser 介面,用於定義Bean的解析邏輯;
解析流程
有關於 Spring 對這部分內容的實現細節,可以參考 Schema解析,下面我對這部分內容做一個簡單的梳理:
- Spring 中對Bean的解析主要是通過
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
方法,具體的解析邏輯委託給BeanDefinitionParserDelegate
進行; -
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
會區分 默認的Namespace和自定義的Namesapce(除Spring的一些默認標籤外,其它的都是自定義Namespace) - 在解析自定義Namespace的時候會調用
DefaultNamespaceHandlerResolver#resolve
方法,DefaultNamespaceHandlerResolver
中會載入所有META-INF/spring.handlers
文件裡面的內容,然後維護一套NamespaceURL => NamespaceHandler
的映射關係。然後在DefaultNamespaceHandlerResolver#resolve
方法中調用 當前NamespaceURL對應的NamespaceHandler#init
方法。 - dubbo對應的 NamespaceHandler 是
DubboNamespaceHandler
,在DubboNamespaceHandler#init
方法中,會找到各個標籤對應的 BeanDefinitionParser 介面,這裡對應DubboBeanDefinitionParser
並快取起來; - 在解析標籤的時候會調用
DubboNamespaceHandler#parse
方法,而真正的解析邏輯委託給內部的DubboBeanDefinitionParser#parse
方法;
補充部分關鍵程式碼:
// DefaultBeanDefinitionDocumentReader#parseBeanDefinitions protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 默認解析 parseDefaultElement(ele, delegate); }else { // 自定義解析 delegate.parseCustomElement(ele); } } } } else { // 自定義解析 delegate.parseCustomElement(root); } }
// DubboNamespaceHandler.java // NamespaceHandlerSupport是一個抽象類,實現了NamespaceHandler介面 public class DubboNamespaceHandler extends NamespaceHandlerSupport { static { Version.checkDuplicate(DubboNamespaceHandler.class); } @Override public void init() { registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true)); registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true)); registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true)); registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true)); registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true)); registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true)); registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true)); registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false)); registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); } }
至此,整個dubbo的xml標籤解析流程就非常清晰了,如果你想通過XML配置的方式來使用dubbo,那麼當你配置好xml之後,隨著 Spring 的啟動,就會自動解析dubbo對應的那些標籤了。
註解啟動
註解是為了讓我們擺脫繁瑣的XML配置,但對程式碼有一定侵入,高版本的dubbo和springboot整合其實非常方便,引入依賴之後只需要在啟動類上添加 @EnableDubbo
註解即可。以 dubbo 2.7.2
和 springboot 2.1.4.RELEASE
為例:
使用示例
依賴
dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.apache.dubbo:dubbo:2.7.2' implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2' implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2' implementation 'org.apache.zookeeper:zookeeper:3.4.12' implementation 'org.apache.curator:curator-recipes:2.12.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
啟動類
@SpringBootApplication @EnableDubbo public class SDubboApplication { public static void main(String[] args) { SpringApplication.run(SDubboApplication.class, args); } @Configuration @PropertySource("classpath:/dubbo-provider.properties") static class ProviderConfiguration { @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://10.9.44.133:2181"); // 註冊簡化版的的url到註冊中心 registryConfig.setSimplified(true); return registryConfig; } @Bean public MetadataReportConfig metadataReportConfig() { MetadataReportConfig metadataReportConfig = new MetadataReportConfig(); metadataReportConfig.setAddress("zookeeper://10.9.44.133:2181"); return metadataReportConfig; } @Bean public ConfigCenterConfig configCenterConfig() { ConfigCenterConfig configCenterConfig = new ConfigCenterConfig(); configCenterConfig.setAddress("zookeeper://10.9.44.133:2181"); return configCenterConfig; } } }
dubbo-provider.properties
dubbo.application.name=sdubbo dubbo.protocol.name=dubbo dubbo.protocol.port=20882
有關於通過註解定義Provider和Consumer這裡就不介紹了。從上面的程式碼中可以看到,那三個Bean只是一些配置工作,這不是我們關注的重點,重點在 @EnableDubbo
註解,為什麼添加這個註解之後dubbo服務就自動註冊了?
@EnableDubbo
不妨先看看這個註解,可以發現它引用了 @EnableDubboConfig
和 @DubboComponentScan
,前者與配置
相關,後者與 服務註冊和服務引用
相關。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @EnableDubboConfig @DubboComponentScan public @interface EnableDubbo { ...... }
@EnableDubboConfig
這個註解和 外部化配置相關
,可以參考一篇博文: 外部化配置
即:一些通用的配置資訊全部配置在 application.properties
或者 bootstrap.properties
配置文件中,dubbo會根據這些配置資訊自動創建 ApplicationConfig
、RegistryConfig
、ProviderConfig
等Bean,而不需要我們通過註解的方式硬編碼去創建。
其核心原理在 DubboConfigConfigurationRegistrar
類中,這個不是本篇文章的重點,不過多介紹。
其實在上面的示例中,就已經用到了外部化配置特性,雖然沒有在 application.yaml
中定義dubbo的這些屬性,但是在註解類中通過 @PropertySource("classpath:/dubbo-provider.properties")
將這些屬性導入進來了,所以dubbo會自動根據這些屬性去創建相應的Bean, 比如ApplicationConfig
,雖然在示例中沒有通過硬編碼的方式創建ApplicationConfig
,但是dubbo在讀到 dubbo-provider.properties
文件中的 dubbo.application
屬性時會自動創建一個 ApplicationConfig
。
外部化配置下,dubbo和springboot整合如下:
依賴
dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.apache.dubbo:dubbo:2.7.2' implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2' implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2' implementation 'org.apache.zookeeper:zookeeper:3.4.12' implementation 'org.apache.curator:curator-recipes:2.12.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
application.yml
server: port: 8786 spring: main: allow-bean-definition-overriding: true dubbo: application: name: sdubbo protocol: name: dubbo port: 20882 registry: address: zookeeper://10.9.44.133:2181 simplified: true metadata-report: address: zookeeper://10.9.44.133:2181 config-center: address: zookeeper://10.9.44.133:2181
啟動類
@SpringBootApplication @EnableDubbo public class SDubboApplication { public static void main(String[] args) { SpringApplication.run(SDubboApplication.class, args); } }
@DubboComponentScan
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(DubboComponentScanRegistrar.class) public @interface DubboComponentScan { ...... }
小擴展
在 Spring 中,通過 @Import
導入一個外部類有三種方式
- 直接導入;
@Configuration @Import(ExternalBean.class) public class TestImportConfiguration { }
- 導入一個
ImportSelector
介面的實現類,然後重寫selectImports
方法,在該方法中返回要導入類的全類名;
@Configuration @Import(TestImportSelect.class) public class TestImportSelectConfiguration { } public class TestImportSelect implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.sxy.spring.register.ExternalBean"}; } }
- 導入一個
ImportBeanDefinitionRegistrar
介面的實現類,然後重寫registerBeanDefinitions
方法,在該方法中通過BeanDefinitionRegistry
註冊 BeanDefinition;
@Configuration @Import(TestImportBeanDefinitionRegistrar.class) public class TestImportBeanDefinitionRegistrarCongiguration { } public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 註冊一個bean, 指定bean name registry.registerBeanDefinition("externalBean", new RootBeanDefinition(ExternalBean.class)); } }
DubboComponentScanRegistrar
DubboComponentScanRegistrar
實現了 ImportBeanDefinitionRegistrar
介面,而在它重寫的 registerBeanDefinitions
方法中做了兩件事:
- 註冊
ServiceAnnotationBeanPostProcessor
; - 註冊
ReferenceAnnotationBeanPostProcessor
;
ServiceAnnotationBeanPostProcessor
實現了 BeanDefinitionRegistryPostProcessor
介面;ReferenceAnnotationBeanPostProcessor
實現了 InstantiationAwareBeanPostProcessorAdapter
介面。 了解Spring的同學都知道這是Spring的擴展介面。
小擴展
-
BeanFactoryPostProcessor
:在實例化bean之前,可以修改BeanDefinition資訊; -
BeanDefinitionRegistryPostProcessor
:BeanFactoryPostProcessor
介面的子類,在BeanFactoryPostProcessor之前執行,可用於創建 BeanDefinition; -
BeanPostProcessor
: Bean初始化前後執行。 -
InstantiationAwareBeanPostProcessor
:BeanPostProcessor 的子類,實例化前後執行; -
ApplicationContextAwareProcessor
:實現了BeanPostProcessor,在postProcessBeforeInitialization中注入各種Aware介面;
ServiceAnnotationBeanPostProcessor
以一個dubbo provider為例
@org.apache.dubbo.config.annotation.Service public class LeannImpl implements ILearn { @Override public String learn(String name) { return "學習: " + name; } }
在 ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry
的方法中,主要做了兩件事:
- 根據配置的包掃描路徑找到所有帶有
@org.apache.dubbo.config.annotation.Service
註解的類,為這些類創建BeanDefinition
,然後註冊到IOC容器中;這部分實現隱藏在DubboClassPathBeanDefinitionScanner#scan
方法中。 - 為每個原始類再創建一個
ServiceBean
類型的BeanDefinition
資訊。
即:每一個dubbo服務最終會在IOC容器中對應兩個Bean,一個是原始類型,一個是 ServiceBean
類型, ServiceBean
其實是一個 FactoryBean
, 是實現服務暴露的關鍵,這裡不展開。 以上面的例子為例,最終兩個Bean對應的BeanName分別為:leannImpl
和 ServiceBean:com.sxy.sdubbo.service.ILearn
ReferenceAnnotationBeanPostProcessor
以一個dubbo consumer為例
@Component("demoServiceComponent") public class DemoServiceComponent implements DemoService { @Reference(timeout = 3000) private DemoService demoService; @Override public String sayHello(String name) { return demoService.sayHello(name); } }
ReferenceAnnotationBeanPostProcessor
主要做了兩件事:
- 創建一個代理服務;
- 為
DemoServiceComponent
注入值,即DemoServiceComponent.demoService = 代理服務
;
即: DemoServiceComponent
僅僅代表 Spring 容器中的一個普通Bean;而 @Reference註解標註demoService屬性
最終指向的是動態創建的一個代理服務,就是通過這個代理服務實現與provider通訊。
至此,註解模式下,dubbo服務註冊與引用流程已經很清晰了,具體的實現細節可以查看源碼。
自動化配置
自動化配置其實是springboot提供的一個特性,其目的就是盡量讓用戶原理各種繁瑣配置, 其核心原理就是讀取 META-INF/spring.factories 中的自動化配置類,下面簡單介紹一下。
Spring的自動化配置
啟動springboot應用的時候會添加一個 @SpringBootApplication
註解,而該註解中包含了 @EnableAutoConfiguration
註解,而 @EnableAutoConfiguration
就是實現自動化配置的關鍵
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ...... }
核心就在 AutoConfigurationImportSelector
類,它實現了 ImportSelector
介面,其實主要就做了一件事情:
- 通過
SpringFactoriesLoader#loadFactories
方法載入 classpath 下所有 JAR 文件的 META-INF/spring.factories 文件,然後提取出文件中的所有xxxEnableAutoConfiguration
,這樣就相當於將所有的xxxEnableAutoConfiguration
註冊到 Spring 容器中了。當然,這些xxxEnableAutoConfiguration
一般會結合各種@Conditional
來判斷是否創建Bean。
dubbo-spring-boot-starter
springboot 項目就是由一個個 starter 組成的,一個 starter 通常包含了該模組需要的依賴,通常自動化配置也是在 starter 中完成的。
dubbo在springboot應用中的自動化配置也是通過一個 starter 來完成了,官方Git地址:dubbo-spring-boot-project
那麼,在自動化配置模式先,dubbo與springboot整合應該怎麼做? 可以用外部化配置,也可以用註解的方式來配置,這裡以註解的方式配置為例:
依賴
dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' // 因為目前(2019/07/19)dubbo-spring-boot-starter最新只有2.7.1版本 implementation 'org.apache.dubbo:dubbo-spring-boot-starter:2.7.1' implementation 'org.apache.dubbo:dubbo:2.7.2' implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2' implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2' implementation 'org.apache.zookeeper:zookeeper:3.4.12' implementation 'org.apache.curator:curator-recipes:2.12.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
啟動類
// @EnableDubbo 不需要加這個註解 @SpringBootApplication public class SDubboApplication { public static void main(String[] args) { SpringApplication.run(SDubboApplication.class, args); } @Configuration @PropertySource("classpath:/dubbo-provider.properties") static class ProviderConfiguration { @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://10.9.44.133:2181"); // 註冊簡化版的的url到註冊中心 registryConfig.setSimplified(true); return registryConfig; } @Bean public MetadataReportConfig metadataReportConfig() { MetadataReportConfig metadataReportConfig = new MetadataReportConfig(); metadataReportConfig.setAddress("zookeeper://10.9.44.133:2181"); return metadataReportConfig; } @Bean public ConfigCenterConfig configCenterConfig() { ConfigCenterConfig configCenterConfig = new ConfigCenterConfig(); configCenterConfig.setAddress("zookeeper://10.9.44.133:2181"); return configCenterConfig; } } }
dubbo-provider.properties
dubbo.application.name=sdubbo dubbo.protocol.name=dubbo dubbo.protocol.port=20882 # 多了一個包掃描,在當前 starter 版本中,這個屬性必須配置 dubbo.scan.base-packages =com.sxy.sdubbo
與註解方式的區別:
- 添加
dubbo-spring-boot-starter
依賴; - 不需要配置
@EnableDubbo
註解; - 需要配置包掃描路徑
dubbo.scan.base-packages
;
其實現原理就是springboot中的自動化配置: dubbo-spring-boot-starter
的pom.xml文件中引入了 dubbo-spring-boot-autoconfigure
模組, dubbo-spring-boot-autoconfigure
的pom.xml文件中引入了 dubbo-spring-boot-autoconfigure-compatible
模組;
- 在
dubbo-spring-boot-autoconfigure
模組中,META-INF/spring.factories 文件內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration= org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
- 在
dubbo-spring-boot-autoconfigure-compatible
模組中,META-INF/spring.factories 文件內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration= org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration, org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBindingAutoConfiguration org.springframework.context.ApplicationListener= org.apache.dubbo.spring.boot.context.event.OverrideDubboConfigApplicationListener, org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener, org.apache.dubbo.spring.boot.context.event.AwaitingNonWebApplicationListener org.springframework.boot.env.EnvironmentPostProcessor= org.apache.dubbo.spring.boot.env.DubboDefaultPropertiesEnvironmentPostProcessor org.springframework.context.ApplicationContextInitializer= org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer
DubboRelaxedBindingAutoConfiguration
主要是和屬性解析有關,這裡不做介紹;核心還是 DubboAutoConfiguration
, 在該類中有一下兩段關鍵程式碼
/** * @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME) 強制要求了我們需要配置包掃描路徑,否則該Bean不會被創建 * Creates {@link ServiceAnnotationBeanPostProcessor} Bean * * @param propertyResolver {@link PropertyResolver} Bean * @return {@link ServiceAnnotationBeanPostProcessor} */ @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME) @ConditionalOnBean(name = BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME) @Bean public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor( @Qualifier(BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME) PropertyResolver propertyResolver) { Set<String> packagesToScan = propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet()); return new ServiceAnnotationBeanPostProcessor(packagesToScan); } /** * Creates {@link ReferenceAnnotationBeanPostProcessor} Bean if Absent bean工廠不存在referenceAnnotationBeanPostProcessor時創建 * * @return {@link ReferenceAnnotationBeanPostProcessor} */ @ConditionalOnMissingBean @Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME) public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() { return new ReferenceAnnotationBeanPostProcessor(); }
其實核心就是通過自動化的方式創建了 ServiceAnnotationBeanPostProcessor
和 ReferenceAnnotationBeanPostProcessor
,沒有通過 @EnableDubbo
註解觸發。但是感覺這樣不太好用。