主頁 > 後端開發 > 原始碼決議:Dubbo3 的 Spring 適配原理與初始化流程

原始碼決議:Dubbo3 的 Spring 適配原理與初始化流程

2022-12-05 12:13:42 後端開發

Dubbo 國內影響力最大的開源框架之一,非常適合構建大規模微服務集群的,提供開發框架、高性能通信、豐富服務治理等能力,同時 Dubbo 無縫支持 Spring、Spring Boot 模式的開發,這篇文章幫助大家理解 Dubbo 是怎么和 Spring 做集成的,非常適合關心原理是先的開發者,

感興趣的朋友可以直接訪問官網體驗 Spring+Dubbo 開發微服務 或搜索關注官方微信公眾號:Apache Dubbo

Spring Context Initialization

首先,我們先來看一下Spring context初始化主要流程,如下圖所示:

相關代碼:org.springframework.context.support.AbstractApplicationContext#refresh()

簡單描述一下每個步驟包含的內容:

  1. 創建BeanFactory:讀取加載XML/注解定義的BeanDefinition,
  2. prepareBeanFactory: 注冊提前加載的各種內置post-processor以及環境變數等,
  3. invokeBeanFactoryPostProcessors: 加載BeanDefinitionRegistryPostProcessor和 BeanFactoryPostProcessor,注意這里的加載順序比較復雜,還涉及到多次加載,詳細請查看代碼, 常用于加載較早初始化的組件,如屬性配置器PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer, 還有一個比較重要的ConfigurationClassPostProcessor實作了BeanDefinitionRegistryPostProcessor介面,用于加載@Configuration類,決議@Bean定義并注冊BeanDefinition, 這個階段常用于注冊自定義BeanDefinition,
  4. registerBeanPostProcessors: 加載并注冊各種BeanPostProcessor,常用于修改或包裝(代理)bean實體,如Seata的GlobalTransactionScanner,
  5. registerListeners: 加載并注冊ApplicationListener,處理earlyApplicationEvents
  6. finishBeanFactoryInitialization: 注冊EmbeddedValueResolver,凍結配置
  7. preInstantiateSingletons: 遍歷加載單例bean,也就是加載普通的bean,包括@Controller, @Service, DAO等,

FactoryBean

Spring容器支持兩種bean:普通bean和工廠bean(FactoryBean),我們經常寫的@Controller/@Service這種被Spring直接初始化的bean就是普通bean, 而FactoryBean則是先由Spring先創建FactoryBean實體,然后由其再創建最終的bean實體,

[Spring BeanFactory] --create---> [XxxFactoryBean instance] --create--> [Final Bean Instance]
FactoryBean介面如下:
public interface FactoryBean<T> {
  /**
   * Return an instance (possibly shared or independent) of the object managed by this factory.
   */
	T getObject() throws Exception;

  /**
   * Return the type of object that this FactoryBean creates, or null if not known in advance.
   * This allows one to check for specific types of beans without instantiating objects, for example on autowiring.
   */
  Class<?> getObjectType();

}

BeanDefinition

Spring bean分為注冊和創建實體兩大階段,將從Spring XML/注解決議到的bean資訊放到BeanDefinition,然后將其注冊到BeanFactory,后面會根據BeanDefinition來初始化bean實體, 不管是普通bean還是工廠bean,都是先注冊bean definition,然后按照依賴順序進行初始化,

兩者BeanDefinition的差異是:

  • 普遍bean的BeanDefinition的beanClassName為最終bean的class
  • 工廠bean的BeanDefinition的beanClassName為工廠bean的class

注冊Bean主要有幾種方式:

  1. 在Spring XML中定義< bean />,由Spring決議生成并BeanDefinition
  2. 在Spring java config中宣告@Bean方法,由Spring決議生成并BeanDefinition
  3. 呼叫BeanDefinitionRegistry.registerBeanDefinition()方法手工注冊BeanDefinition
  4. 通過SingletonBeanRegistry.registerSingleton()方法注冊bean實體,

注意:注冊bean實體與前面三種注冊BeanDefinition有本質的區別, 打個比方,注冊BeanDefinition是新兒子,Spring會管理bean的初始化及依賴注入及解決屬性占位符,呼叫BeanPostProcessor進行處理等, 而注冊bean實體就是別人的兒子,Spring將其視為已經完成初始化的bean,不會解決其依賴和屬性占位符,后面會講到Dubbo 2.7/3兩個版本Reference注解注冊bean的差異,

初始化bean

創建bean大概有下面幾個步驟:

  • 創建實體 createBeanInstance
  • 解決依賴 resolveDependency
  • 解決屬性占位符 applyPropertyValues

其中多次呼叫BeanPostProcessor進行處理,如果某些BeanPostProcessor此時還沒注冊,則可能導致遺漏處理了當前的bean, 后面會講到dubbo 2.7中提前加載config bean導致的一系列問題,

其中關鍵邏輯請參考代碼:AbstractAutowireCapableBeanFactory#doCreateBean()

解決依賴

在Spring注解流行起來之后,通常是使用@Autowire注解來注入依賴的bean,此種注入方式大概的流程如下:

  • 查找匹配屬性型別的beanName串列
  • 根據@Qualifier/@Primary/propertyName等選擇合適的bean 關鍵邏輯請參考代碼:DefaultListableBeanFactory#doResolveDependency(),

其中第一步,查找匹配型別的beanName串列時會呼叫ListableBeanFactory#getBeanNamesForType()來列舉檢查所有的beanDefinition, 檢查bean type的邏輯請查看 AbstractBeanFactory#isTypeMatch(), 涉及的邏輯比較復雜,這里只簡單講一下重要的分支:

  • 如果是普通bean,則檢查BeanDefinition的beanClass是否匹配
  • 如果是FactoryBean,則通過多種方式來預測bean type

FactoryBean的型別預測主要包括下面幾種:

  1. 如果有DecoratedDefinition,則覆寫BeanDefinition,檢查合并后的beanClass是否匹配
  2. 通過FactoryBean.OBJECT_TYPE_ATTRIBUTE屬性獲取beanType (since 5.2)
  3. 實體化這個FactoryBean,呼叫getObjectType()方法來獲取beanType 上面提到的第三種情況可能會出現實體化失敗(如解決屬性占位符失敗)而被多次創建的問題,即每次預測bean type都會嘗試實體化,而每次都失敗,直到它所依賴的組件都就緒才成功,

Dubbo ReferenceBean本身也是一個FactoryBean,在2.7中經常因為預測bean type導致被自動初始化,后面會詳細講這個問題,

解決屬性

在Spring中一般是通過 PropertyPlaceholderConfigurer/PropertySourcesPlaceholderConfigurer來解決XML/@Value中的屬性占位符${...}, 二者都實作了BeanFactoryPostProcessor介面,會在invokeBeanFactoryPostProcessors階段被加載,然后遍歷處理所有BeanDefinition中的屬性占位符,

[決議注冊BeanDefinition] => [PropertyResourceConfigurer 解決屬性占位符] => [加載BeanPostProcessor] => [初始化單例bean]
由此可知,如果在PropertyPlaceholderConfigurer/PropertySourcesPlaceholderConfigurer加載前去初始化某個bean,則這個bean的屬性占位符是不會被解決的, 這個就是Dubbo config bean 被過早加載導致無法解決占位符的根因,

Dubbo Spring的一些問題及解決辦法

Dubbo spring 2.7 初始化程序

初始化入口是ReferenceBean#prepareDubboConfigBeans(),即當第一個ReferenceBean初始化完成時,嘗試加載其他dubbo config bean,

    @Override
    public void afterPropertiesSet() throws Exception {

        // Initializes Dubbo's Config Beans before @Reference bean autowiring
        prepareDubboConfigBeans();

        // lazy init by default.
        if (init == null) {
        init = false;
        }

        // eager init if necessary.
        if (shouldInit()) {
        getObject();
        }
    }
    
    private void prepareDubboConfigBeans() {
        beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, ConfigCenterBean.class);
        beansOfTypeIncludingAncestors(applicationContext, MetadataReportConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, MetricsConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, SslConfig.class);
    }

存在的問題:

  1. 沒有一個固定的初始化時機,而是與ReferenceBean初始化相關, 如果ReferenceBean被過早初始化,經常出現dubbo配置丟失、屬性占位符未解決等錯誤,
  2. 可能在BeanPostProcessor加載完成前初始化ReferenceBean,將導致類似Seata這種通過BeanPostProcessor機制的組件攔截失敗,

Dubbo spring 3的初始化程序

Dubbo 3 中進行大量重構,上面的痛點問題已經被解決,初始化主要流程如下:

[Spring決議XML/@Configuration class注冊BeanDefinition] => [加載BeanFactoryPostProcessor(包含PropertyResourceConfigurer)] 
 => [1.決議@DubboReference/@DubboService注解并注冊BeanDefinition]
 => [加載并注冊BeanPostProcessor] 
 => [加載ApplicationListener] => [2.加載DubboConfigBeanInitializer初始化config bean]
 => [初始化單例bean] => [依賴注入ReferenceBean]
 => [3.監聽ContextRefreshedEvent事件,啟動dubbo框架]

主要包含3個階段:

  1. 在BeanFactoryPostProcessor階段決議@DubboReference/@DubboService注解并注冊BeanDefinition,因為此時還是BeanDefinition處理階段, 故注冊的ReferenceBean可以被后續加載的業務bean使用@Autowire依賴注入,同時,也擴展支持在@Configuration bean 方法使用@DubboReference/@DubboService注解,
  2. 在加載完所有PropertyResourceConfigurer和BeanPostProcessor之后才會執行DubboConfigBeanInitializer初始化config bean,解決了屬性 占位符未解決和BeanPostProcessor攔截失敗的問題,
  3. 監聽在Spring context事件,在其加載完畢時啟動dubbo框架,

支持在@Configuration bean 方法使用@DubboReference/@DubboService注解

參考Dubbo spring 3的初始化程序的第1階段,

屬性占位符解決失敗

參考Dubbo spring 3的初始化程序的第2階段,

ReferenceBean被過早初始化問題

預測ReferenceBean beanType導致
Dubbo ReferenceBean本身也是一個FactoryBean,在2.7中經常因為預測bean type導致被自動初始化, 例如用戶自定義的某個BeanFactoryPostProcessor bean使用了@Autowire注解依賴注入某個業務bean, 而且這個自定義的BeanFactoryPostProcessor bean優先級比解決屬性占位符的PropertyResourceConfigurer高,則此時出現解決屬性占位符失敗,

Dubbo 3中ReferenceBean通過下面兩種方式解決預測type的問題:

FactoryBean的型別預測主要包括下面幾種:

如果有DecoratedDefinition,則覆寫BeanDefinition,檢查合并后的beanClass是否匹配

通過FactoryBean.OBJECT_TYPE_ATTRIBUTE屬性獲取beanType (since 5.2)

ReferenceBean被直接依賴導致過早初始
如果在Dubbo config bean初始化前被依賴自動創建ReferenceBean實體,并創建一個Lazy proxy類注入到依賴的類中,不需要解決屬性占位符,不會拉起Dubbo框架, 其他的config bean則固定在PropertyResourceConfigurer和BeanPostProcessor加載完成后才會執行初始化,避免了上述問題,

Reference注解可能出現@Autowire注入失敗的問題

在Dubbo 2.7中,在BeanPostProcessor中決議@DubboReference/@Reference注解,創建并注入ReferenceBean實體到Spring容器,這種方式有幾個問題:

@DubboReference/@Reference注解與XML定義的< dubbo:reference />初始化方式不一致,前者是由dubbo初始化,后者是由Spring容器負責初始化,

執行時機導致的依賴注入失敗問題,按照正常的在invokeBeanFactoryPostProcessors階段注冊完畢所有BeanDefinition,而dubbo 2.7的ReferenceAnnotationBeanPostProcessor 是在BeanPostProcessor執行時才創建ReferenceBean,可能出現某些比它早初始化的bean使用@Autowire注入失敗的情況,

在Dubbo 3中,改成在BeanFactoryPostProcessor決議@DubboReference/@Reference注解并注冊ReferenceBean的BeanDefinition,記錄欄位將要注入的referenceBeanName, 在BeanPostProcessor執行時通過BeanFactory().getBean(referenceBeanName)獲取到ReferenceBean實體,

搜索關注官方微信公眾號:Apache Dubbo,了解更多業界最新動態,掌握大廠面試必備 Dubbo 技能

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/539213.html

標籤:其他

上一篇:mybatis之xml映射檔案>、<=等特殊符號寫法

下一篇:每日演算法之二叉搜索樹的后序遍歷序列

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more