原文地址:http://decompile.it/blog/2014/03/13/webapi-autofac-lifetime-scopes/html
這是一篇關於AutoFac的生命週期做用域的文章。git
關於生命週期域一直以來都是一個使人頭疼的命題,其中有些概念極易形成誤解和混淆,好比域內單例(PerLifetimeScope)和請求內單例(InstancePerRequest)有什麼區別、以及它們可不能夠替換使用等等......github
這些問題以前也一直困擾着我,直到我在stackoverflow上發現了這篇文章的連接,做者利用示例代碼 + 圖文並茂的方式,完全地解答了個人全部疑惑,感謝之餘我就順手把它翻譯了下來。web
在閱讀原文以前,能夠先看看下面幾個問題,若是你對這些問題都已經很清楚了,那麼恭喜你,你已經強大到不須要浪費時間閱讀該文,能夠直接出門右轉了:api
域內單例(PerLifetimeScope)是什麼意思?ide
請求內單例(InstancePerRequest)是什麼意思?工具
域內單例和請求內單例有什麼區別?在WebApi類型的項目中,它們可不能夠相互替換使用?測試
在.NET Core中,AutoFac的請求內單例(InstancePerRequest)將再也不有效,可是有些對象又須要被註冊爲請求內單例(好比EF的DbContext),那可使用域內單例(PerLifetimeScope)來替換嗎?會產生什麼影響?ui
若是對其中任何一個問題還抱有疑惑,那麼我相信這篇文章對你必定會有所幫助的(正如當初對我同樣)。翻譯
這篇文章中提到的Http請求內單例(InstancePerHttpRequest)和Api請求內單例(InstancePerApiRequest)如今在AutoFac中已通過時了,取而代之的是整合後的請求內單例(InstancePerRequest)
原做者的源碼是在GitHub開源的,地址就在文章的末尾。我Fork了一份,將AutoFac更新到了最新版本,而且添加了中文文檔,有須要的也能夠去下載或瀏覽個人GitHub
本文是以默認讀者已經瞭解了依賴注入與AutoFac的基礎知識爲前提的,若是有朋友仍是初學者,我建議能夠先去讀一讀AutoFac的技術文檔,或者也能夠去看下我以前寫過的兩篇半小時大話.NET依賴注入的文章~
當咱們使用AutoFac(或者任何其餘用於依賴注入的容器)時,常常有一個很是困擾咱們的命題,那就是生命週期做用域。
若是你是一個初學者,我建議能夠先讀一讀 Nicholas Blumhardt 的一篇很棒的文章:An Autofac Lifetime Primer。鑑於你可能須要反覆多讀幾遍來消化這些知識,我建議能夠保存個書籤。
針對 AuotoFac,我在衆多場合下都聽到過這樣一個疑問:
域內單例(InstancePerLifetimeScope)、Http請求內單例(InstancePerHttpRequest)和Api請求內單例(InstancePerApiRequest)有什麼區別?
一直以來我也對這個問題感到疑惑,並且目前爲止我尚未找到一個使人滿意的回答。因此,今天我將嘗試着本身來解答下這個問題。
先拋出個人終極結論:
若是你想讓你註冊的依賴在任何域內均可已被解析,那麼請使用域內單例(InstancePerLifetimeScope)。你的依賴項會同生命週期域一同釋放。若是這個域是根域,那麼它將一直存在直到程序終結。
若是你想要讓你註冊的依賴只能在request類型(HTTP/API)的請求上下文中被解析,那麼請使用請求內單例(InstancePerApiRequest/InstancePerHttpRequest)。依賴會在請求結束後被釋放。
這裏我將不會再去解釋做用域和生命週期的概念了,Nicholas已經很好地完成了這部分工做,我上面也已經把他文章的連接貼出來了。因此,我將假定大家已經具備依賴注入的基礎知識,如今大家只是想知道針對Web程序它們是如何運做的。
爲了更好的講解,我本身寫了個簡單的程序,須要的能夠本身下載下來試着跑一跑。程序裏我建立了4個Resolvables
類——它們每一個都很簡單,惟一的功能就是展現出服務是從哪兒被解析出來的。註冊它們的代碼以下所示:
private static void RegisterResolvables(ContainerBuilder builder) { builder.RegisterType<SingletonResolvable>() .SingleInstance(); builder.RegisterType<PerLifetimeResolvable>() .InstancePerLifetimeScope(); builder.RegisterType<PerRequestResolvable>() .InstancePerApiRequest(); builder.RegisterType<PerDependencyResolvable>() .InstancePerDependency(); }
程序還有一個負責解析的類,它惟一的任務就是負責解析上面的4個Resolvables
類。下面是該類的構造方法:
public ResolvableConsumer( SingletonResolvable singleton, PerLifetimeResolvable lifetime, PerRequestResolvable request, PerDependencyResolvable dependency) { // ... }
如今,我要作一件神奇的事情了!我創造了一個ScopeToken
類,並簡單地封裝了一下它,使它能夠展現它本身是被哪一個做用域解析出來的,而後讓4個Resolvables
類都依賴這個ScopeToken
類。在註冊ScopeToken
類時,咱們能夠經過修改它的生命週期做用域來觀察到底會對程序產生什麼變化。下面,咱們就先把它註冊爲瞬時實例(InstancePerDependency)試試看。
private static void RegisterToken(ContainerBuilder builder) { var tokenRegistration = builder.RegisterType<ScopeToken>(); // TODO: 挨個嘗試 // tokenRegistration.SingleInstance(); // tokenRegistration.InstancePerLifetimeScope(); // tokenRegistration.InstancePerApiRequest(); tokenRegistration.InstancePerDependency(); }
咱們能夠經過請求TestController
下的一個GET請求來測試咱們的程序。我這裏用了HTTPie工具來模擬Web請求(關於這個工具的使用,能夠參考Scott Hanselman的安裝筆記)
如今咱們的準備工做已經所有完成了,接下來咱們一塊兒看下使用不一樣的生命週期做用域註冊,會對解析ScopeToken
有什麼樣的影響。
在使用AutoFac註冊組件時,若是咱們不本身指定生命週期域,該域將是默認的選項。在技術文檔裏是這麼解釋的:
註冊時用該域標註組件,那麼每個依賴組件或每一次經過Resolve()解析出的都將是一個全新的實例。
咱們來看下,調用GET接口會發生什麼:
不出所料,每一個解析對象
內都被注入了一個屬於他們本身的惟一的token。看,依賴注入起做用了!
咱們能夠看到幾點有趣的地方:
SingletonResolvable
的token是從根域
內(root scope)解析出的
其餘解析類的token所有是從一個叫AutofacWebRequest的域內解析出的
以下圖所示:
出於好奇,咱們來看下若是再調用一次接口會發生什麼:
Token #1沒有變。這是由於根域的生命週期和程序是保持一致的。換句話說,SingletonResolvable
對象以及它所依賴的ScopeToken
對象將一直存在,直到程序中止運行爲止。
相反,Tokens #2, #3 和 #4已經所有被釋放掉了,由於AutofacWebRequest域
的生命週期是和Web請求保持一致的。也就是,該域在請求發起時被建立,當請求結束後就當即被釋放掉了。
private static void RegisterToken(ContainerBuilder builder) { var tokenRegistration = builder.RegisterType<ScopeToken>(); tokenRegistration.SingleInstance(); }
下一個比較容易理解的是全局單例,其含義就像它的名字所表達的:任什麼時候候都將獲得一個惟一實例。實際上,Autofac會將單例對象歸屬到根域
(root scope)內(或者叫「container」 scope),而其餘的全部域都是這個根域下的子域。下面是調用接口的輸出結果:
再次不出所料地,每一個解析對象得到的都是同一個ScopeToken
實例。
有兩點須要指出:
private static void RegisterToken(ContainerBuilder builder) { var tokenRegistration = builder.RegisterType<ScopeToken>(); tokenRegistration.InstancePerLifetimeScope(); }
從這兒開始事情就要變得有趣了。AutoFac文檔對域內單例的解釋以下:
用該生命週期做用域註冊,之後的每一個依賴組件或經過Resolve()解析出的對象,在同一個生命週期做用域內是相同的,它們共享同一個單例,而在不一樣的生命週期做用域內則是不一樣的。
還記得上面瞬時單例的例子嗎?SingletonResolvable
類是解析在根域中的,其餘的Resolvables
類都被解析到了AutofacWebRequest域
。咱們來看下域內單例又會發生什麼:
正如預期的,咱們有兩個「激活」的域,並且每一個域內都有一個ScopeToken
實例。
讓咱們來看下當再次調用接口會發生什麼:
和以前的瞬時單例同樣,處在根域內的Token #1一直存在着,而處在AutofacWebRequest域
內的Token #2在請求結束後被釋放掉了。
一直以來有一個廣泛的錯誤認知,就是認爲在WebAPI項目中若是組件被註冊爲域內單例(InstancePerLifetimeScope)的話,那麼意思就是它將存活在一次request請求內,即它的生命週期就是一次request請求的生命週期。可是正如上面的例子所展現的,這種認知是錯誤的。
被註冊爲域內單例的組件,它的生命週期是由解析它的域所決定的。
由於SingletonResolvable
實例是在根域
內解析它的token,因此這個token實例就存在於根域
內,而不是一次web請求的生命週期域。以前已經說過,這裏我要再重複一遍:這個token會一直存在直到整個應用程序中止運行爲止(即IIS工做進程被回收時)。任何對象只要是在根域內要求獲取依賴的ScopeToken
,那麼它就會獲得這個惟一單例的對象。
private static void RegisterToken(ContainerBuilder builder) { var tokenRegistration = builder.RegisterType<ScopeToken>(); tokenRegistration.InstancePerApiRequest(); }
最後,也是最重要的,讓咱們來看下Api請求內單例。下面是調用接口後的狀況:
請求出現了一個使人不快的異常,內容是:
被請求獲取的實例所在的域內,找不到一個標籤爲‘AutofacWebRequest’的域。這一般代表,有一個被註冊爲每次HTTP請求內單例的組件被一個全局單例的組件請求獲取(或者是相似的其餘場景)。web項目一般是從DependencyResolver.Current或者ILifetimeScopeProvider.RequestLifetime中獲取依賴,可是不容許直接從根容器中獲取。
爲了明白爲何會發生這樣的異常,咱們須要回到AutoFac的技術文檔上來。裏面說,Api請求內單例(InstancePerApiRequest)其實是每一個匹配域內單例(InstancePerMatchingLifetimeScope)的一種特殊狀況,文檔原文是這樣的 :
用Api請求內單例來註冊組件,那麼每一個依賴組件或者每次經過Resolve()解析,只要是在打了統一標籤名稱的域內,就會獲得同一個對象,即它們共享同一個單例。在這個特定標籤域下面的全部子域中,依賴組件也會共享其父域中的單例。若是在當前域和它的父域中都找不到這個標籤域,那麼一個類型爲DependencyResolutionException的異常將會被拋出。
具體來講,Api請求內單例(InstancePerApiRequest)實質上是在一個特定標籤域內單例,正如你所猜想的,這個特定標籤域就是AutofacWebRequest域
。這個域會在一次請求開始時被建立,而且在請求結束後被當即釋放。綜上,若是使用Api請求內單例(InstancePerApiRequest)來註冊組件,那麼這個組件只容許在AutofacWebRequest域
內或其子域內被解析。
咱們的異常就發生在解析SingletonResolvable
對象的時候。以前咱們把它註冊爲全局單例(SingleInstance),因此它就處於根域
內,而根域
(正如名字所表達的)是全部其餘域的父域。對依賴的解析是不容許向下朝着子域方向查找的,只容許向上照着其父域去查找依賴。綜上所述,SingletonResolvable
對象不能夠去AutofacWebRequest標籤域
內查找其依賴,因此它就不能得到它的依賴項ScopeToken
,再而,咱們就獲得了上面拋出的異常。
上面我沒有提Http請求內單例(InstancePerHttpRequest),是由於它本質上和Api請求內單例(InstancePerApiRequest)是相同的,只是它只用於HTTP請求(相對WebApi而言)。實際上,它內部使用的依然是匹配域內單例(InstancePerMatchingLifetimeScope),一樣的,這個用於匹配的標籤名稱也叫作AutofacWebRequest
。因此,被註冊爲Http請求內單例的組件能夠解析被註冊爲Api請求內單例的對象,反之亦然。
但願這篇文章能幫你更好地理解WebAPI項目下的AutoFac的生命週期做用域。須要的朋友能夠自由下載源碼並使用。
Gerrod 發表於 2014年5月13日 .NET板塊
讀完再回頭去看開頭那幾個問題,是否是就已經有答案了?