Autofac Process overview diagram-How the package achieves the purpose of IOC

Czxdas
7 min readDec 2, 2021

--

IOC 概念幫助我們節省了開發與維護的時間,是非常有感的。以往我們如果自己寫一個幫助自己專案的 IOC 工具,因為時間上的安排通常會做得比較直接簡約,能夠達成抽換(外部嵌入注入或專案內部物件注入)就算是達成目的,已經不錯了。作法大致上是使用一個static的集合來記錄Type與對應的設定要怎麼產生實例。

而許多套件方面卻又提供了更多的情境來使用。不但產生的實例可以注入到各種架構,還可搭配情境、生命週期、記憶體方面限制等等,使用上讓開發者感覺有非常豐富的支援。其中挑選了 Autofac 來使用與探討,原因是:

1. 有足夠不藏私的說明文件.

2. 支援各種架構的使用,套件有持續維護 (包含新的語法來簡化程式).

3. 有多個貢獻者提供擴充與問題修正.

4. 為開源套件,代表使用上也可以自己客製擴充.

當然其它套件也有許多優點,用法也大致上都類似。用法幾乎可以用二個方向來描述 : 第一個是先註冊(Register)。第二個是實現實例(Resolve).

舉個簡單的例子:

//註冊

builder.RegisterType<TextWriterLog>().AsSelf().As<ILog>().SingleInstance();

//產生實例

ILog provider = null;
using (var scope = AutofacContainer.builder.BeginLifetimeScope())
{

if (scope.TryResolve<ILog>(out provider))
{
provider.WriteByLog(“\n” + DateTime.Now.ToString(“yyyy/MM/dd HH:mm:ss”) , “ILog write message!”);
}
}

如此即能實現簡單的IOC。

而 Autofac 還能夠讓產生實例的註冊再包含一些設定,如委派的函示、條件的安排,甚至其它情境如 Decorator pattern 與 Adapter pattern 的使用。這時會想,它是如何運作的? 它是怎麼達成的?

以下會以概述圖解方式展現。

Autofac 會用一個容器將所有資訊存入,並建各種引擎來做對應的事。最終 build 到 容器中。

先觀察註冊時的圖敘述:

Autofac Container Register diagram - for IOC、DI

在註冊服務這個動作中,最終要產生 RegistrationBuilder 這個物件, 包含了要如何變成實體的資料 ActivatorData 、變成什麼樣的服務 RegistrationData 與 其它協助過濾輔助的資料 RegistrationStyle。

其中的 RegistrationData.DeferredCallback,要存到 ContainerBuilder.configurationCallbacks List集合中,先Hook 執行內容 RegistrationBuilder.RegisterSingleComponent於此,也就是告訴容器這個服務未來要用此方式來產生製造實例的資訊。

接著看ContainerBuiler Build容器時,要產生容器與將資訊放入容器中:

Autofac ContainerBuilder execute build process

ContainerBuilder為建構容器的阿大引擎。之前透過此大引擎註冊好服務後,這邊開始做建構容器的動作。容器Container內有一個小引擎物件,ComponentRegistry, 它的作用是將ConponentRegistration存到自己的_registrations 與 _serviceinfo 集合內。透過Register方法先將預設的(新產生的必要服務) 轉至 ConponentRegistration 然後存到 自己的_registrations 與 _serviceinfo 集合。

接下來要將註冊好的資訊 (RegistrationBuilder) 將此內容由註冊時所Hook的驅動執行,產生ComponentRegistration存入ComponentRegistry的_registrations 與 _serviceinfo 集合內。

Autofac ContainerBuilder execute build process cont.

所以我們拉出視野,綜觀ContainerBuilder.build()在做的事:

1. 先建立一個新的 Container。

2. 產生新的ConponentRegistration,裡面放預設必要的基礎服務,放到 Container 的 ComponentRegistry 中的_registrations 與 _serviceinfo 集合。

3. 將開發者註冊好的 RegistrationBuilder 轉成 ConponentRegistration 並逐一放到 Container 的 ComponentRegistry 中的_registrations 與 _serviceinfo 集合。

做好以上準備後,接下來就是於程式中去實現實例,以下圖稍作說明:

Autofac Container execute Resolve process

這邊看到有做遞迴,當一個實例的建構子參數還有需要用到所註冊的服務來產生實例,會接著再去跑一次抓服務資訊來對應並產生實例。所以其實它是可以巢狀地,一層一層的去注入已註冊好的服務。

如 new OutterClass( new MediamClass( new InnerClass()) , 此三個Class 皆可利用註冊服務的方式去resolve出來,而且可以神奇的只下一個指令:

container. Resolve<OutterClass>();

就可以幫忙產生好實例了,實在非常簡便,令很多國內外開發者讚嘆。

看到這邊,我相信要真的體會實際運作是有些技巧與想像。也就是仍有一點抽象。所以想了一個例子來做白話描述,讓大家可以想像得出來大致上的運作、作法,或者其實是類似做了什麼:

某人帶了一個籃子(container) , 買了放了各種蛋,並收集了要做哪些與蛋有關的菜單(service) ,貼上標籤,內容為蛋種類、配料與食譜,要準備做出哪個菜單,

標籤註明主要以快炒方式煮這些蛋(Hook RegistrationBuilder.RegisterSingleComponent ),

到了某餐廳(應用程式主體或某 controller action),

要找人做菜 (準備 resolve),

然後到找到某個有證照廚師,把這些資訊給他,請他做菜 (委派),
菜單要炒雞蛋, 依照標籤找到白雞蛋與火腿玉米配料,最後 快炒方式煮出火腿玉米炒蛋 (用火腿玉米裝飾了炒蛋,裝飾者模式)。

找到另一名廚師,這名廚師要先做到洗乾淨手與戴口罩(lambda expression and before prepare event)才行,然後請他做菜(委派),菜單要炒鹹蛋, 依照標籤找到鹹鴨蛋與苦瓜等配料,最後 快炒方式煮出苦瓜炒鹹蛋 (用苦瓜裝飾了炒蛋,裝飾者模式)。

之後要怎麼用這些煮好的成品看用途!

下一篇是介紹如何在此套件擴充客製的功能。

--

--

Czxdas
Czxdas

Written by Czxdas

Keep looking for Prajna wisdom

No responses yet