Dubbo3 源碼系列 Dubbo“糾葛”(入門篇)

日期 更新說明
2022年5月28日 spring xml部分解讀
2022年6月3日 spring annotation部分解讀
人生不相見, 動如參與商。
今夕復何夕, 共此燈燭光。
少壯能幾時, 鬢髮各已蒼。
訪舊半爲鬼, 驚呼熱中腸。
焉知二十載, 重上君子堂。
昔別君未婚, 兒女忽成行。
怡然敬父執, 問我來何方。
問答未及已, 兒女羅酒漿。
夜雨剪春韭, 新炊間黃粱。
主稱會面難, 一舉累十觴。
十觴亦不醉, 感子故意長。
明日隔山嶽, 世事兩茫茫。
杜甫 《贈衛八處士》摘選(文章加粗部分;此情此景覺得有點共鳴哈)分享 一下。
本來計劃Dubbo3的源碼解讀系列,打算兩週更新一篇計劃;很抱歉第三篇來晚了;可能標題寫的有點過於情緒化;勿噴“標題黨”,相信我絕對是筆者內容決定認真原創。

前言
上篇文章《Dubbo3 源碼系列 -- 環境準備》啓示已經介紹了,Dubbo API的方式使用了,可能對於多數使用者,使用Spring集成Dubbo的 xml 和 註解形式可能更多;那麼這篇文章從Spring如何集成Spring的支持角度開始解讀。
開始之前,源碼基於目前Dubbo3.0.7版本;解讀可能是目前文檔源碼有些出入,請注意版本。
Dubbo和Spring部分
Dubbo支持Spring的xml方式
基本使用和案例
其他書籍或者教程;通常會爲你編寫一個,HelloWorld的案例;筆者覺得,看這篇文章的讀者的水平應該是達到了這個級別的;不在手寫Dubbo的入門案例;Dubbo官方源碼 dubbo-demo提供好了源碼案例。

  1. 服務提供者編寫:

  2. 服務消費者編寫:

  3. 服務提供者、消費者主類
    (篇幅問題;提供鏈接)
    provider/Application
    consumer/Application
    總結:
    通知配置Dubbo標籤,Spring容器啓動時,加載Dubbo相關的Bean,同時生成對應的接口代理,爲RPC調用做好準備。
    思考:(源碼閱讀需要解決問題)

  4. Dubbo如何支持Spring的?

  5. Dubbo通過代理Bean,實現Spring注入和RPC調用,那麼注入哪些Bean,具體細節如何呢?
    有了問題,讓我們帶着問題點閱讀源碼可能效果更好。
    源碼解讀:
    Dubbo在利用Spring可擴展的XML Schema機制時,在Spring啓動時加載Dubbo自定義的標籤內容(Dubbo的ximl文檔[官方文檔]);Dubbo的標籤較多,這裏依照 dubbo:service 配置(dubbo-service文檔)爲例;可以大體熟悉一下基本用法。
    dubbo:service
    dubbo:service 配置
    服務提供者暴露服務配置。對應的配置類:org.apache.dubbo.config.ServiceConfig
    屬性 對應URL參數 類型 是否必填 缺省值 作用 描述 兼容性
    interface class 必填 服務發現 服務接口名 1.0.0以上版本
    ref object 必填 服務發現 服務對象實現引用
    Spring可擴展的XML Schema機制

創建一個 XML Schema 文件,描述自定義的合法構建模塊,也就是xsd文件
dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
自定義個處理器類,並實現NamespaceHandler接口(比較容易)

  1. 自定義接口handler實現;懂點實現 parse接口:
    ● init(): NamespaceHandler被使用之前調用,完成NamespaceHandler的初始化
    ● BeanDefinition parse(Element, ParserContext): 當遇到頂層元素時被調用
    ● BeanDefinition decorate(Node,BeanDefinitionHandler,ParserContext): 當遇到一個屬性或者嵌套元素的時候調用
    @Override
    public void init() {
    // spring在解析Dubbo.xml標籤時,調用模板init方法初始化用於解析Dubbo自定義的XML標籤註解的解析器(DubboBeanDefinitionParser)
    registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class));
    registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class));
    registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class));
    registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class));
    registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class));
    registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class));
    registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class));
    registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class));
    registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class));
    registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class));
    registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class));
    registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class));
    registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class));
    registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

/**
* Override {@link NamespaceHandlerSupport#parse(Element, ParserContext)} method
*
* @param element {@link Element}
* @param parserContext {@link ParserContext}
* @return
* @since 2.7.5
*/
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
registerAnnotationConfigProcessors(registry);

// initialize dubbo beans
DubboSpringInitializer.initialize(parserContext.getRegistry());

/**
     * 重點調用Spring的org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse方法;
     * 就是對應init內部方法的DubboBeanDefinitionParser{@link DubboBeanDefinitionParser#parse(Element, ParserContext)}#parse方法
 */
BeanDefinition beanDefinition = super.parse(element, parserContext);
setSource(beanDefinition);
return beanDefinition;

}
2. 自定義BeanDefinitionParser實現NamespaceHandlerSupport接口;主要解析對應的BeanDefination
org.apache.dubbo.config.spring.schema.DubboBeanDefinitionParser;有興趣自行分析
3. 註冊handler和schema
爲了讓Spring在解析xml的時候能夠感知到我們的自定義元素,我們需要把namespaceHandler和xsd文件放到2個指定的配置文件中,這2個文件都位於META-INF目錄中
Spring通過XML解析程序將其解析爲DOM樹,通過NamespaceHandler指定對應的Namespace的BeanDefinitionParser將其轉換成BeanDefinition。再通過Spring自身的功能對BeanDefinition實例化對象。
在期間,Spring還會加載兩項資料:
○ META-INF/spring.handlers
指定NamespaceHandler(實現org.springframework.beans.factory.xml.NamespaceHandler)接口,或使用org.springframework.beans.factory.xml.NamespaceHandlerSupport的子類。
○ META-INF/spring.schemas
在解析XML文件時將XSD重定向到本地文件,避免在解析XML文件時需要上網下載XSD文件。通過現實org.xml.sax.EntityResolver接口來實現該功能
4. 對於註冊Context容器;這裏是Spring自定義機制;目前由於篇幅問題;多做過多的細節介紹:(請看圖)
Spring Bean 加載流程分析(通過 XML 方式加載)
Spring-ClassPathXmlApplicationContext源碼探討-解析XML文件_半笙彷徨的博客-CSDN博客
Spring 的annotation部分源碼
基本使用和案例

服務接口

服務提供者:

源碼解讀
使用註解需要重點關注:@PropertySource、@DubboService、@DubboReference 就可以了
思考:

  1. Dubbo的註解都幹了些啥?(這個基本看下注釋和源碼基本都可以明白)
  2. Dubbo如何如何讓Spring識別Dubbo自定義的註解呢?
  3. 還記得之前註解對應的properties文件嗎?這個也作爲小問題點
    源碼入口 -- @EnableDubbo
    ● @EnableDubboConfig :加載Dubbo相關配置
    需要配置多個對象(ApplicationConfig、RegistryConfig、ProtocolConfig、ServiceConfig等),而這個註解的目的就是幫我們簡化這些配置,只要將相應的配置項通過.properties文件交由@PropertySource註解由Spring加載到Environment中,Dubbo即可通過Environment中相應的屬性與ApplicationConfig對象同名的屬性進行綁定,而這個過程都是自動。
    ● @DubboComponentScan:掃描類路徑以獲取將自動註冊爲Spring bean的註釋組件。

@EnableDubboConfig
@Import(DubboConfigConfigurationRegistrar.class)註解導入;用於解析和加載通用的Bean :
// ImportBeanDefinitionRegistrar 註解 配合 @Import使用
public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    // initialize dubbo beans(Spring從初始化,加載Dubbo相關的Bean)
    DubboSpringInitializer.initialize(registry);

    // Config beans creating from props have move to ConfigManager

// AnnotationAttributes attributes = AnnotationAttributes.fromMap(
// importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName()));
//
// boolean multiple = attributes.getBoolean("multiple");
//
// // Single Config Bindings
// registerBeans(registry, DubboConfigConfiguration.Single.class);
//
// if (multiple) { // Since 2.6.6 https://github.com/apache/dubbo/issues/3193
// registerBeans(registry, DubboConfigConfiguration.Multiple.class);
// }

}

}
DubboBeanUtils#registerCommonBeans
/**
* Register the common beans
*
* @param registry {@link BeanDefinitionRegistry}
* @see ReferenceAnnotationBeanPostProcessor
* @see DubboConfigDefaultPropertyValueBeanPostProcessor
* @see DubboConfigAliasPostProcessor
* @see DubboBootstrapApplicationListener
*/
static void registerCommonBeans(BeanDefinitionRegistry registry) {

    registerInfrastructureBean(registry, ServicePackagesHolder.BEAN_NAME, ServicePackagesHolder.class);

    registerInfrastructureBean(registry, ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class);

    // Since 2.5.7 Register @Reference Annotation Bean Processor as an infrastructure Bean
    registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME,
        ReferenceAnnotationBeanPostProcessor.class);

    // TODO Whether DubboConfigAliasPostProcessor can be removed ?
    // Since 2.7.4 [Feature] https://github.com/apache/dubbo/issues/5093
    registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME,
        DubboConfigAliasPostProcessor.class);

    // Since 2.7.4 Register DubboBootstrapApplicationListener as an infrastructure Bean

// registerInfrastructureBean(registry, DubboBootstrapApplicationListener.BEAN_NAME,
// DubboBootstrapApplicationListener.class);

    // register ApplicationListeners
    registerInfrastructureBean(registry, DubboDeployApplicationListener.class.getName(), DubboDeployApplicationListener.class);
    registerInfrastructureBean(registry, DubboConfigApplicationListener.class.getName(), DubboConfigApplicationListener.class);

    // Since 2.7.6 Register DubboConfigDefaultPropertyValueBeanPostProcessor as an infrastructure Bean
    registerInfrastructureBean(registry, DubboConfigDefaultPropertyValueBeanPostProcessor.BEAN_NAME,
        DubboConfigDefaultPropertyValueBeanPostProcessor.class);

    // Dubbo config initializer
    registerInfrastructureBean(registry, DubboConfigBeanInitializer.BEAN_NAME, DubboConfigBeanInitializer.class);

    // register infra bean if not exists later
    registerInfrastructureBean(registry, DubboInfraBeanRegisterPostProcessor.BEAN_NAME, DubboInfraBeanRegisterPostProcessor.class);
}

總結:
使用 @EnableDubbo加載Dubbo對應的共有配置Bean和postprocesser,掃描註解,加載@Service和@reference註解。更多內容啓示關於Spring的框架的底層內容熟悉,Dubbo只是靈活的使用Spring的API。
幫助文檔
xsd-custom-registration
Spring中的XML schema擴展機制
Spring容器啓動流程+Bean的生命週期【附源碼】 - 天喬巴夏丶 - 博客園 (cnblogs.com)
緣起 Dubbo ,講講 Spring XML Schema 擴展機制 - 掘金 (juejin.cn)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章