低配 Spring—Golang IOC 框架 dig 原理解析
0 前言
本期分享的主題是 Golang 中的 IOC 框架 dig,內(nèi)容涉及到個人對編程風(fēng)格的理解、對 dig 使用方法的介紹以及對 dig 底層原理的剖析.
原文:?https://mp.weixin.qq.com/s/bireIkWWTQUdgc-UJEhN5g
文章內(nèi)容的目錄樹結(jié)構(gòu)如下圖:

?
1 IOC 框架使用背景
在引入 IOC 概念之前,我們需要先補(bǔ)充一些前置設(shè)定:這里主要針對“面向?qū)ο缶幊獭焙汀俺蓡T依賴注入”兩個問題進(jìn)行探討.
1.1 面向?qū)ο缶幊?/h1>
首先拋出一個經(jīng)典問題:“面向?qū)ο蠛兔嫦蜻^程有什么區(qū)別?”
這是個抽象的問題,本質(zhì)上可以劃分到哲學(xué)的范疇,涉及到個人看待世界的角度.
我是個俗人,不太會聊哲學(xué),但是代碼領(lǐng)域的問題,我挺能聊.
下面,我們就化抽象為具象,嘗試用代碼實現(xiàn)一個場景——“把一只大象裝進(jìn)冰箱”.

?
在面向過程編程的視角下:
解決問題的核心是化整為零,把大問題拆解為一個個小問題,再針對小問題進(jìn)行逐個擊破.
在執(zhí)行綱領(lǐng)的指導(dǎo)下,我們在編寫代碼時需要注重的是步驟的拆分與流程的串聯(lián).
下面展示一下偽代碼:
?
與面向過程相對,在面向?qū)ο缶幊痰囊暯侵拢?/p>
一切皆為對象.
在本場景中,我選擇把大象和冰箱都看成是有靈魂的角色,并且準(zhǔn)備在交互場景中給予它們更多的參與感.
于是,這里首先塑造出大象和冰箱這兩種角色(聲明對象類);其次再給對應(yīng)的角色注入靈魂(賦予屬性和方法);最后,把主動權(quán)交還給各個角色,由它們完成場景下的互動:
?
(1)構(gòu)造對象/注入靈魂
就以大象裝冰箱的場景為例,我們首先我們構(gòu)造出大象和冰箱兩個對象,并賦予其對應(yīng)的能力,比如:
??大象是有生命的,它會有自己的情緒,會有行動的能力;
??冰箱作為容器,除了一些基本信息之外,最重要是具有裝載事物的能力.
?
?
(2)由對象完成交互
接下來,在場景的描述中,我們首先構(gòu)造出參與其中的各個對象,然后通過各對象本身固有的能力完成交互.
通過上述例子,希望能幫助大家對面向?qū)ο蟮木幊陶軐W(xué)產(chǎn)生更直觀的感受.
?
1.2 成員依賴注入
在日常業(yè)務(wù)代碼的編寫中,我個人會比較推崇面向?qū)ο蟮拇a風(fēng)格,原因如下:
??面向?qū)ο缶幊叹哂蟹庋b、多態(tài)、繼承的能力,有利于系統(tǒng)模塊之間的聯(lián)動
??將系統(tǒng)各模塊劃分為一個個對象,劃分邊界、各司其職,使得系統(tǒng)架構(gòu)層級分明、邊界清晰
??對象中的核心成員屬性能夠被復(fù)用,避免重復(fù)構(gòu)造
?
上述的第三點需要基于面向?qū)ο缶幊?成員依賴注入的代碼風(fēng)格加以保證.
成員依賴注入是我在依賴注入Dependency Injection(DI)概念的基礎(chǔ)上小小調(diào)整后得到的復(fù)合概念,其指的是,在程序運行過程中,當(dāng)對象A需要依賴于另一個對象B協(xié)助完成工作時,不會在代碼中臨時創(chuàng)建對象B的實例,而是遵循以下幾個步驟:
??前置將對象B聲明為對象A的成員屬性
??在初始化對象A的構(gòu)造函數(shù)暴露出A對B的依賴
??于是在A初始化前,先壓棧完成B的初始化工作
??將初始化后的B注入A的構(gòu)造函數(shù),完成A的初始化
??在A的生命周期內(nèi),完成對成員屬性B的復(fù)用,不再重復(fù)創(chuàng)建
?
下面進(jìn)一步舉正反例子,對比成員依賴注入這一思想對代碼風(fēng)格帶來的影響:
背景中,我們有三個對象,分別是:
??和數(shù)據(jù)源打交道的 DAO
??和第三方服務(wù)通信交互的 Client
??聚集了核心業(yè)務(wù)流程的 Service,且 Service 會依賴于 DAO 和 Client 的能力
?
(1)無成員依賴注入
首先給出不遵循成員依賴注入的代碼反例:
?
在上述代碼中,存在的兩個局限性在于:
??dao、client 等核心組件的生命周期局限于一個業(yè)務(wù)方法中,因此會被重復(fù)創(chuàng)建. 這類組件內(nèi)部本身還有依賴,其初始化過程通常是比較”重“的. 因此其多次重復(fù)創(chuàng)建/銷毀的行為可能會帶來嚴(yán)重的性能損耗
??Service 與 dao、client 強(qiáng)耦合,模塊定位喪失靈活度. 這一點目前看來說得不夠直觀,可以相較第(2)部分來看
?
(2)遵循成員依賴注入
?
?
這種成員依賴注入風(fēng)格的代碼具有的特點包括:
??依賴的核心組件一次注入,永久復(fù)用,沒有重復(fù)創(chuàng)建所帶來的成本
??就近將成員抽象為 interface 后,基于多態(tài)的思路,Service 本身的定位更加靈活,取決于注入的成員變量的具體實現(xiàn)

舉例說明,把 dao 和 client 定義為 interface 后,
??當(dāng)注入和食物數(shù)據(jù)庫交互的 foodDAO 和食物服務(wù)交互的 foodClient 時,service 就被定位成處理食物業(yè)務(wù)的模塊
??當(dāng)注入和飲品數(shù)據(jù)庫交互的 drinkDAO 和飲品服務(wù)交互的 drinkClient 時,service 就被定位成處理飲品業(yè)務(wù)的模塊
??...
?
foodClient + foodDAO -> foodSerivce
drinkClient + drinkDAO -> drinkSerivce
...
?
更進(jìn)一步,倘若我們需要編寫模塊的單測代碼,還可以實現(xiàn) mock 成員變量的注入,從而實現(xiàn)外置依賴的代碼邏輯的打樁,讓單測邏輯能夠好地聚焦在 Service 領(lǐng)域的業(yè)務(wù)代碼:
??當(dāng)注入 mockDAO 和 mockClient 時,service 就被成為了一個僅用于測試的 mock 業(yè)務(wù)模塊.
?
?
1.3 引入 IOC 的原因
在 1.2 小節(jié)的基礎(chǔ)上做個延伸性的探討,倘若所有代碼都嚴(yán)格遵循這種成員依賴注入的風(fēng)格,一旦系統(tǒng)架構(gòu)變得復(fù)雜,就會有新的問題產(chǎn)生:
(1)大量的依賴對象

?
倘若對象A依賴的成員模塊數(shù)量很大,每個成員都需要由構(gòu)造器的調(diào)用方通過入?yún)⑦M(jìn)行顯式注入,這樣編寫起來代碼復(fù)雜度過高:
?
(2)重復(fù)的依賴對象

?
此外,依賴路徑可能存在交叉的情況,最終形成一張錯綜復(fù)雜的依賴網(wǎng),此時就會產(chǎn)生兩個問題:
??倘若某個子對象被多個父對象所依賴,如何保證子對象維持為單例狀態(tài),能夠被全局復(fù)用
??如何梳理好復(fù)雜的依賴路徑,保證依賴注入流程的正常執(zhí)行
?
舉個代碼示例如下:
?
梳理完上述問題后,我們的訴求也逐漸清晰:
??需要有一個全局的容器,實現(xiàn)對各個組件進(jìn)行緩存復(fù)用
??需要有一個全局管理對象,為我們梳理各對象間的依賴路徑,依次完成依賴注入的工作
而本文的主題—— IOC 框架,扮演的正是這樣一個角色.
IOC,全稱 Inversion of Control 控制反轉(zhuǎn),指的是將業(yè)務(wù)組件的創(chuàng)建、復(fù)制、管理工作委托給業(yè)務(wù)代碼之外的容器進(jìn)行統(tǒng)一管理. 我們通常把容器稱為 container,把各個業(yè)務(wù)組件稱為 bean.
由于各個 bean 組件之間可能還存在依賴關(guān)系,因此 container 的另一項能力就是在需要構(gòu)建 bean 時,自動梳理出最優(yōu)的依賴路徑,依次完成依賴項的創(chuàng)建工作,最終產(chǎn)出用戶所需要的 bean.
在這個依賴路徑梳理的過程中,倘若 container 發(fā)現(xiàn)存在組件缺失,導(dǎo)致 bean 的依賴路徑無法達(dá)成,則會拋出錯誤終止流程. 通常這個流程會在編譯階段或者程序啟動之初執(zhí)行,因此倘若依賴項存在缺失,也能做到盡早拋錯、及時止損,引導(dǎo)開發(fā)人員提前解決代碼問題.
?
1.4 Golang IOC 框架 dig
(1)dig 基本信息
聊到 IOC 框架,JAVA 中的 Spring 是一座繞不過的大山. 相對于生態(tài)成熟資源豐富的 JAVA 而言,Golang 中成熟可用的 IOC 框架就相對有限.
而今天我們要介紹的主角是由 uber 開源的 dig,git開源地址為:https://github.com/uber-go/dig,本文走讀的源碼版本為 tag v1.15.
?

?
(2)dig 與 spring 的差距
dig 能夠為研發(fā)人員提供到前文提及的兩項核心能力:
??bean 單例管理
??bean 依賴路徑梳理
同時,本著實事求是的態(tài)度,我們也如實闡述一下 dig 相比于 spring 所缺失的能力:
(1)只有 IOC,不具有 AOP (Aspect Oriented Programming)的能力
(2)在同一個 key 下(bean type + bean name/group)只支持單例,不支持原型
(3)將 bean 注入 container 的方式相對單調(diào),強(qiáng)依賴于構(gòu)造器函數(shù)的模式
(4)由于依賴于構(gòu)造器函數(shù),因此不能解決循環(huán)依賴問題(事實上,在Golang 中,本就不支持循環(huán)依賴的模式,跨包之間的循環(huán)依賴引用,會在編譯層面報錯)
(5)bean 沒有支持豐富的生命周期方法
?
2 dig 使用教程
2.1 provide/invoke

?
首先給出代碼示例,供大家更直觀地感受通過 dig 實現(xiàn)依賴注入、路徑梳理、bean 復(fù)用的能力:
??存在 bean A、bean B,其中 bean A 依賴于 bean B
??聲明 bean A 和 bean B 的構(gòu)造器方法,A 對 B 的依賴關(guān)系需要在構(gòu)造器函數(shù) NewA 的入?yún)⒅畜w現(xiàn)
??通過 dig.New 方法創(chuàng)建一個 dig container
??通過 container.Provide 方法,分別往容器中傳入 A 和 B 的構(gòu)造器函數(shù)
??同歸 container.Invoke 方法,傳入 bean A 的獲取器方法 func(_a *A),其中需要將獲取器函數(shù)的入?yún)㈩愋驮O(shè)置為 bean A 的類型
??在獲取器方法運行過程中,入?yún)⑼ㄟ^容器取得 bean A 實例,此時可以通過閉包的方式將 bean A 導(dǎo)出到方法外層
?
輸出結(jié)果:
?
2.2 dig.In
2.1 小節(jié)介紹的基本用法中,我們需要將 bean A 依賴的子 bean 統(tǒng)統(tǒng)在構(gòu)造器函數(shù)中通過入?yún)⒌姆绞竭M(jìn)行聲明,倘若依賴數(shù)量較大的話,在聲明構(gòu)造器函數(shù)時可能存在不便,此時可以通過內(nèi)置 dig.In 標(biāo)識的方式替代構(gòu)造函數(shù),標(biāo)志出 A 中所有可導(dǎo)出的成員變量均為依賴項.
dig.In 方式的使用示例如下,其中需要注意的點是:
??作為依賴 bean 的成員字段需要聲明為可導(dǎo)出類型
??內(nèi)置了 dig.In 標(biāo)識的 bean,在通過 Invoke 流程與 container 交互時必須使用 struct 類型,不能使用 pointer 形式
?
輸出結(jié)果:
?
2.3 dig.Out
與 2.2 小節(jié)中的 dig.In 對偶,我們可以通過 dig.Out 聲明,在 Provide 流程中將某個類的所有可導(dǎo)出成員屬性均作為 bean 注入到 container 中.
與 dig.In 相仿,dig.Out 在使用時同樣有兩個注意點:
其中需要注意的點是:
??需要作為注入 bean 的成員字段需要聲明為可導(dǎo)出類型
??內(nèi)置了 dig.Out 標(biāo)識的 bean,在通過 Provide 流程與 container 交互時必須使用 struct 類型,不能使用 pointer 形式
?
?
輸出結(jié)果:
?
2.4 bean name
此外,倘若存同種類型存在多個不同的 bean 實例,上層需要進(jìn)行區(qū)分使用,此時 container 要如何進(jìn)行標(biāo)識和管理呢,答案就是通過 name 標(biāo)簽對 bean 進(jìn)行標(biāo)記,示例代碼如下:
?
輸出結(jié)果:
?
2.5 bean group
倘若依賴的是 bean list 該如何處理,這就需要用到 dig 中的 group 標(biāo)簽.
需要注意的點是,在通過內(nèi)置 dig.Out 的方式注入 bean list 的時候,需要在 group tag 中聲明 flatten 標(biāo)志,避免 group 標(biāo)識本身會將 bean 字段上升一個維度.
?
?
3 dig 原理解析
下面明確一下 dig 框架的實現(xiàn)原理,首先拆解一下宏觀流程中的要點:
??基于注入構(gòu)造函數(shù)的方式,實現(xiàn) bean 的創(chuàng)建
??基于反射的方式,實現(xiàn) bean 類型到到構(gòu)造函數(shù)的映射
??在運行時而非編譯時實現(xiàn) bean 的依賴路徑梳理
?
在 dig 的實現(xiàn)中,bean 依賴路徑的梳理時機(jī)是在服務(wù)運行階段而非編譯階段,因此這個流程應(yīng)該和業(yè)務(wù)代碼解耦,專門聲明一個 factory 模塊聚合處理的 bean 的創(chuàng)建工作. 避免將 bean 獲取操作零星散落在業(yè)務(wù)流程各處,這樣倘若某個 bean 存在依賴缺失,則會導(dǎo)致服務(wù) panic.
?
3.1 核心數(shù)據(jù)結(jié)構(gòu)
在方法鏈路的源碼走讀和原理解析之前,先對 dig 中幾個重要的數(shù)據(jù)結(jié)構(gòu)進(jìn)行介紹:

?
(1)Container&Scope
Container 即存放和管理 bean 的全局容器.
Scope 是一個范圍塊,本質(zhì)上是一棵多叉樹中的一個節(jié)點,擁有自己的父節(jié)點和子節(jié)點列表.
一個 Container 由一棵 Scope 多叉樹構(gòu)成,手中持有的是 root Scope 的引用.
目前在筆者的工程實踐中未涉及到對 Scope 的使用,通常只使用一個 root Scope 就足以滿足完使用訴求.
因此,在本文的介紹中,大家可以簡單地把 Container 和 Scope 認(rèn)為是等效的概念.
?
?
?
(2)key
key 是容器中的唯一標(biāo)識鍵,由一個二元組構(gòu)成. 其中一維是 bean 的類型 reflect.Type,另一維是 bean 名稱 name 或者 bean 組名 group.
此處 name 字段和 group 字段是互斥關(guān)系,二者只會取其一. 因為一個 bean 被 provide 的時候,就會明確其是 single 類型還是 group 類型.
?
(3)constructorNode

?
constructorNode 是構(gòu)造器函數(shù)的封裝節(jié)點,包含的核心字段包括:
??ctor:bean 構(gòu)造器函數(shù)
??ctype:bean 構(gòu)造器函數(shù)類型
??called:構(gòu)造器函數(shù)是否已被執(zhí)行過
??paramList:構(gòu)造器函數(shù)依賴的入?yún)?/p>
??resultList:構(gòu)造器函數(shù)產(chǎn)生的出參
?
?
(4)param
paramList 是構(gòu)造器節(jié)點的入?yún)⒘斜恚?/p>
??ctype:構(gòu)造器函數(shù)的類型
??params:入?yún)⒘斜?/p>
?
入?yún)?param 本身是個 interface,核心方法是 Build,邏輯是從存儲介質(zhì)(容器) containerStore 中提取出對應(yīng)于當(dāng)前 param 的 bean,然后通過響應(yīng)參數(shù)返回其 reflect.Value.
?
param 的實現(xiàn)類包括:
單個實體 bean,除了我們內(nèi)置 dig.In 標(biāo)識和通過 group 標(biāo)簽標(biāo)識的情況,其他的入?yún)?bean 都屬于 paramSingle 的形式.
?
通過 group 標(biāo)簽標(biāo)識的 bean group
?
內(nèi)置了 dig.In 的 bean
?
(5)result
resultList 是構(gòu)造器函數(shù)節(jié)點的出參列表:
??ctype:構(gòu)造器函數(shù)的類型
??Results:出參列表
?
出參 result 本身是個 interface,核心方法是 Exact,方法邏輯是將已取得的 bean reflect.Value 填充到容器 containerWriter 的緩存 map values 當(dāng)中.
?
result 的實現(xiàn)類包括:
單個實體 bean,除了我們內(nèi)置 dig.Out 標(biāo)識和通過 group 標(biāo)簽標(biāo)識的情況,其他的出參 bean 都屬于 resultSingle 的形式.
?
基于 group 標(biāo)簽標(biāo)識的 bean group
?
內(nèi)置了 dig.out 的 bean.
?
?
3.2 構(gòu)造全局容器

?
(1)dig.New
創(chuàng)建 dig 容器通過 dig.New 方法執(zhí)行,方法中會創(chuàng)建一個 Container 實例,并創(chuàng)建一個 rootScope 注入其中.
?
(2)dig.newCope
newScope 方法中創(chuàng)建了一個 Scope 實例,對 Scope 數(shù)據(jù)結(jié)構(gòu)中的幾個 map 成員變量進(jìn)行了初始化.
值得一提的是,此處聲明了獲取bean 的入口函數(shù) invokerFn 為 defaultInvoker. 其核心邏輯我們在 3.4 小節(jié)第(6)部分展開介紹.
?
?
3.3 注入 bean 構(gòu)造器

在 dig 中,將 bean 注入的方式有兩類:
??一種是在 bean 中內(nèi)置 dig.In 標(biāo)識,執(zhí)行一次 Invoke 方法會自動完成 bean 的注入工作
??另一種是通過 Container.Provide 方法,傳入 bean 的構(gòu)造器函數(shù).
Container.Provide 是主鏈路,接下里沿著該方法進(jìn)行源碼走讀.
(1)Container.Provide
經(jīng)由 Container.Provide -> Scope.Provide 的鏈路調(diào)用后,完成了對構(gòu)造器函數(shù)的類型和配置的檢查,隨后步入 Scope.provide 方法中.
?
?
(2)Scope.provide
Scope.provide 方法中完成的工作是:
??調(diào)用 newConstructorNode 方法,將構(gòu)造器函數(shù)封裝成一個 node 節(jié)點
??調(diào)用 Scope.findAndValidateResults 方法,通過解析構(gòu)造器出參的類型以及用戶定制的 bean 名稱/組名,封裝出對應(yīng)于出參個數(shù)的 key
??將一系列 key-node 對添加到 Scope.providers map 當(dāng)中,供后續(xù)的 invoke 流程使用
??將新生成的 node 添加到 Scope.nodes 數(shù)組當(dāng)中
?
(3)newConstructorNode
newConstructorNode 方法完成了將構(gòu)造器函數(shù) ctor 封裝成節(jié)點的任務(wù),其中包含幾個核心步驟:
??調(diào)用 newParamList 方法,將入?yún)⒎庋b成 param 列表的形式,但還沒有真正從 container 中獲取 bean 執(zhí)行 param 的填充動作
??調(diào)用 newResultList 方法,將出參封裝成 result 列表的形式,同樣只做封裝,沒有執(zhí)行將 result 注入容器的處理
??結(jié)合構(gòu)造器函數(shù) ctor、入?yún)⒘斜?param list 和出參列表 result list,構(gòu)造 constructorNode 并返回
?
(4)newParamList
newParamList 方法中,會根據(jù) reflect 包的能力,獲取到構(gòu)造器函數(shù)的入?yún)⑿畔ⅲ⑵湔{(diào)用 newParam 方法將每個入?yún)⒎庋b成 param 的形式.
?
在 newParam 方法中,會根據(jù)入?yún)⒌念愋停捎貌煌臉?gòu)造方法,包括 paramSingle 和 paramObject 的類型.
?
(5)newResultList
newParamList 方法中,會根據(jù) reflect 包的能力,獲取到構(gòu)造器函數(shù)的出參信息,并將其調(diào)用 newReject 方法將每個出參封裝成 result 的形式.
?
在 newResult 方法中,會根據(jù)出參的類型,采用不同的構(gòu)造方法,包括 resultSingle 和 resultObject、resultGroup 的類型.
?
3.4 提取 bean

?
從容器中提取 bean 的入口是 Container.Invoke 方法,需要將 bean 提取器函數(shù)作為 Invoke 的第一個入?yún)ⅲ⑻崛∑骱瘮?shù)的入?yún)⒙暶鞒?bean 對應(yīng)的類型.
在 dig 提取 bean 的鏈路中,正是根據(jù)提取器函數(shù)的入?yún)㈩愋妥鞣瓷?,從容器中提取出對?yīng)的 bean.
?
(1)Container.Invoke
在 Container.Invoke-> Scope.Invoke 的鏈路中:
??針對提取器函數(shù) function 和配置項 opts 進(jìn)行了校驗
??通過 shallowCheckDependencies 方法進(jìn)行了依賴路徑的梳理,保證容器中已有的組件足以支撐構(gòu)造出本次 Invoke 需要獲得的 bean
??調(diào)用 newParamList 方法,通過提取器函數(shù)的入?yún)?,?gòu)造出所需的 params 列表
??調(diào)用 paramList.BuildList 方法,真正地從容器中提取到對應(yīng)的 bean 集合,通過 args 承載
??調(diào)用 Scope.invokerFn 方法,傳入提取器函數(shù) function 和對應(yīng)的入?yún)?args,通過反射機(jī)制真正地執(zhí)行提取器函數(shù) function,在執(zhí)行過程中,入?yún)?args 就已經(jīng)是從容器中獲取到的 bean 了
?
?
(2)param.Build
paramList.BuildList 方法,會遍歷 params 列表,對每個 param 依次執(zhí)行 param.Build 方法,從容器中獲取到 bean 填充到 args 數(shù)組中并返回.
?
以 param interface 的實現(xiàn)類 paramSingle 為例,paramSingle.Build 方法的執(zhí)行步驟包括:
??倘若 bean 已經(jīng)構(gòu)造過了,則通過 container.getValue 方法直接從 container.values 中獲取緩存好的 bean 單例進(jìn)行復(fù)用
??調(diào)用 container.getValueProviders 方法,獲取 bean 對應(yīng)的 constructorNode
??調(diào)用 constructorNode.Call 方法,通過執(zhí)行 bean 的構(gòu)造器函數(shù)創(chuàng)建 bean 并將其注入到 container.values 緩存 map 中
??再次調(diào)用 container.getValue 方法,從 container.values 緩存 map 中獲取 bean 并返回
?
(3)constructorNode.call
constructorNode.call 方法核心步驟包括:
??通過 constructorNode.called 標(biāo)識,保證每個構(gòu)造器函數(shù)不被重復(fù)執(zhí)行
??調(diào)用 shallowCheckDependencies 方法,檢查構(gòu)造器節(jié)點 constructorNode 入?yún)?yīng)的 paramList 的依賴路徑是否完成
??調(diào)用 paramList.BuildList 方法,將構(gòu)造器節(jié)點依賴的入?yún)?args 構(gòu)造出來(此時會遞歸進(jìn)入 3.4小節(jié)第(2)部分,從容器中提取 bean 填充構(gòu)造器函數(shù)的入?yún)?)
??調(diào)用 Scope.invoker 方法,將構(gòu)造器函數(shù) constructorNode.ctor 及其入?yún)?args 傳入,通過reflect 包的能力真正執(zhí)行構(gòu)造器函數(shù),完成 bean 的構(gòu)造
??調(diào)用 resultList.ExactList 方法,將構(gòu)造生成的 bean 添加到 container.values 緩存 map 中
??將 constructorNode.called 標(biāo)識標(biāo)記為 true,代表構(gòu)造器函數(shù)已經(jīng)執(zhí)行過了
?
(4)result.Extract
在 resultList.ExtractList 方法中,會遍歷傳入的 results,分別執(zhí)行 result.Extract 方法,依次將 bean 添加到 container.values 緩存 map 中.
?
同樣以 resultSingle 為例,方法核心邏輯是以 result 的名稱和類型組成唯一的 key,以 bean 為 value,將 key-value 對添加到 contaienr.values 緩存 map.
?
(5)Scope.invokerFn
Scope 的 invokerFn 是獲取 bean 的入口函數(shù),默認(rèn)使用 defaultInvoker 函數(shù).
?
defaultInvoker 函數(shù)的形參分別為構(gòu)造器函數(shù)及其依賴的入?yún)?,方法?nèi)部會依賴 reflect 庫的能力,執(zhí)行構(gòu)造器函數(shù),并將響應(yīng)結(jié)果返回.
?
?
4 總結(jié)
最后來盤點一下本期我們討論到的內(nèi)容:
??介紹了引入 Golang IOC 框架 dig 的背景——面向?qū)ο缶幊?成員依賴注入的代碼風(fēng)格
??介紹了 dig 的基本用法:(1)創(chuàng)建容器 dig.New;(2)注入 bean 方法:Container.Provide;(3)提取 bean 方法:Container.Invoke
??基于源碼走讀的方式,串講了通過 dig 創(chuàng)建容器、注入 bean 構(gòu)造器和提取 bean 三條方法鏈路的底層實現(xiàn)細(xì)節(jié)