ASP.NET Core依赖注入初识与思考
一、前言
在上一篇中,我們講述了什么是控制反轉(zhuǎn)(IoC)以及通過(guò)哪些方式實(shí)現(xiàn)的。這其中,我們明白了,「控制反轉(zhuǎn)(IoC)」 是一種軟件設(shè)計(jì)的模式,指導(dǎo)我們?cè)O(shè)計(jì)出更優(yōu)良,更具有松耦合的程序,而具體的實(shí)現(xiàn)方式有「依賴注入」和「依賴查找」。
在上篇實(shí)例中,我們通過(guò)日志的方式舉例說(shuō)明,其中通過(guò)代碼創(chuàng)建了一個(gè)ILogger的接口,并實(shí)現(xiàn)接口實(shí)例,基于控制反轉(zhuǎn)的模式,依賴的創(chuàng)建也移交到了外部,但是也發(fā)現(xiàn)存在了問(wèn)題,如果類(lèi)似存在這樣多個(gè)接口和實(shí)現(xiàn)類(lèi),依賴太多,一一創(chuàng)建,沒(méi)有統(tǒng)一的管理,這反而增加了實(shí)際工作量麻煩。
因此我們需要一個(gè)可以統(tǒng)一管理系統(tǒng)中所有的依賴的地方,因此,IoC容器誕生了。
容器負(fù)責(zé)兩件事情:
綁定服務(wù)與實(shí)例之間的關(guān)系
獲取實(shí)例,并管理實(shí)例對(duì)象生命周期(創(chuàng)建與銷(xiāo)毀)
所以在這一篇中,我們主要講述Asp.Net Core中內(nèi)置的IoC容器。
二、說(shuō)明
在Asp.Net Core中已經(jīng)為我們集成提供了一個(gè)內(nèi)置的IoC容器,我們可以看到在Startup.cs的ConfigureServices中,涉及到依賴注入的核心組件,一個(gè)「負(fù)責(zé)實(shí)例注冊(cè)」的IServiceCollection和一個(gè)「負(fù)責(zé)提供實(shí)例」的IServiceProvider
?簡(jiǎn)單的說(shuō)就是兩步:1. 把實(shí)例注冊(cè)到容器中;2. 從容器中獲取實(shí)例
?而在這其中,IServiceCollection為實(shí)現(xiàn)將開(kāi)發(fā)者定義好的實(shí)例注冊(cè)進(jìn)去提供了「三種方法」。
分別是:
「AddTransient」 ?、「AddScoped」 、「AddSingleton」
而這三種不同實(shí)例方法也對(duì)應(yīng)的著三種不同的實(shí)例「生命周期」。
「三種不同的生命周期如下:」
2.1 暫時(shí)性
「AddTransient」
每次在向服務(wù)容器進(jìn)行請(qǐng)求時(shí)都會(huì)創(chuàng)建新的實(shí)例,這種生存期適合輕量級(jí)、 無(wú)狀態(tài)的服務(wù)。
2.2 作用域內(nèi)
「AddScoped」
在每次Web請(qǐng)求時(shí)被創(chuàng)建一次實(shí)例,生命周期橫貫整次請(qǐng)求。
?局部單例對(duì)象, 在某個(gè)局部?jī)?nèi)是同一個(gè)對(duì)象(作用域單例,本質(zhì)是容器單例);一次請(qǐng)求內(nèi)是一個(gè)單例對(duì)象,多次請(qǐng)求則多個(gè)不同的單例對(duì)象。
?2.3 單例
「AddSingleton」
創(chuàng)建單例生命周期服務(wù)的情況如下:
在首次請(qǐng)求它們時(shí)進(jìn)行創(chuàng)建;
或者在向容器直接提供實(shí)現(xiàn)實(shí)例時(shí)由開(kāi)發(fā)人員進(jìn)行創(chuàng)建。很少用到此方法。
其后的每一個(gè)后續(xù)請(qǐng)求都使用同一個(gè)實(shí)例。如果開(kāi)發(fā)者的應(yīng)用需要單例服務(wù)情景,推薦的做法是交給服務(wù)容器來(lái)負(fù)責(zé)單例的創(chuàng)建和生命周期管理,而不是手動(dòng)實(shí)現(xiàn)單例模式然后由開(kāi)發(fā)者在自定義類(lèi)中進(jìn)行操作。
?不要從單一實(shí)例解析指定了作用域的服務(wù)。當(dāng)處理后續(xù)請(qǐng)求時(shí),它可能會(huì)導(dǎo)致服務(wù)處于不正確的狀態(tài)??梢詮姆秶鷥?nèi)或暫時(shí)性服務(wù)解析單一實(shí)例服務(wù)。
?三、開(kāi)始
3.1 接口
定義三個(gè)接口,分別測(cè)試Singleton,Scope,Transient三種,一個(gè) TestService服務(wù)
????public?interface?ITransientService{string?GetGuid();} ????public?interface?IScopedService{string?GetGuid();}????public?interface?ISingletonService{string?GetGuid();} ????public?interface?ITestService??{public??string?GetSingletonID();public?string?GetTransientID();public?string?GetScopedID();}3.2 實(shí)現(xiàn)
根據(jù)上面定義的幾種接口,并一一實(shí)現(xiàn)對(duì)應(yīng)的接口
????public?class?TransientService?:?ITransientService{public?string?OperationId?{?get;?}public?TransientService(){OperationId?=?Guid.NewGuid().ToString()[^4..];}public?string?GetGuid(){return?$"這是一個(gè) Transient service :?"?+?OperationId;}}????public?class?ScopedService?:?IScopedService{public?string?OperationId?{?get;?}public?ScopedService(){OperationId?=?Guid.NewGuid().ToString()[^4..];}public?string?GetGuid(){return?$"這是一個(gè) scoped service :?"+?OperationId;??}} ????public?class?SingletonService?:?ISingletonService{public?string?OperationId?{?get;?}public?SingletonService(){OperationId?=?Guid.NewGuid().ToString()[^4..];}public?string?GetGuid(){return?$"這是一個(gè) Singleton service :?"?+?OperationId;}} ????public?class?TestService?:?ITestService{private?ITransientService?_transientService;private?IScopedService?_scopedService;private?ISingletonService?_singletonService;public?TestService(ITransientService?transientService,?IScopedService?scopedService,?ISingletonService?singletonService){_transientService?=?transientService;_scopedService?=?scopedService;_singletonService?=?singletonService;}public?string?GetSingletonID(){return?_singletonService.GetGuid();}public?string?GetTransientID(){return?_transientService.GetGuid();}public?string?GetScopedID(){return?_scopedService.GetGuid();}}3.3 注入
在Startup.cs類(lèi)文件ConfigureServices方法中,注入依賴
????????public?void?ConfigureServices(IServiceCollection?services){services.AddControllers();services.AddTransient<ITransientService,?TransientService>();services.AddSingleton<ISingletonService,?SingletonService>();services.AddScoped<IScopedService,?ScopedService>();services.AddScoped<ITestService,?TestService>();}3.4 調(diào)用
定義一個(gè)控制器,實(shí)現(xiàn)調(diào)用
[ApiController] [Route("[controller]")] public?class?TestController?:?ControllerBase {private?ITransientService?_transientService;private?IScopedService?_scopedService;private?ISingletonService?_singletonService;?private?ITestService?_testService;public?TestController(ITransientService?transientService,?IScopedService?scopedService,?ISingletonService?singletonService,?ITestService??testService){_transientService?=?transientService;_scopedService?=?scopedService;_singletonService?=?singletonService;_testService?=?testService;}[HttpGet]public?JsonResult?Get(){var?data1?=???_transientService.GetGuid();var?data2?=?_testService.GetTransientID();var?data3?=?_scopedService.GetGuid();var?data4?=?_testService.GetScopedID();var?data5?=?_singletonService.GetGuid();var?data6?=?_testService.GetSingletonID();return?new?JsonResult(new?{?data1,?data2,?data3?,data4,data5,data6,});} }在上面中我們了解到,注入的方式一般有三種,構(gòu)造函數(shù)注入, 方法注入,屬性注入,而在ASP.NET Core中自帶的這個(gè)IoC容器,默認(rèn)采用了構(gòu)造函數(shù)注入的方式。
3.5 測(cè)試
啟動(dòng)運(yùn)行項(xiàng)目,訪問(wèn)接口/Test
效果如下:
3.6 對(duì)比
對(duì)比兩次的請(qǐng)求訪問(wèn)可以發(fā)現(xiàn),上面我們一共得到了 4個(gè)Transient實(shí)例,2個(gè)Scope實(shí)例,1個(gè)Singleton實(shí)例。
「在請(qǐng)求中,AddSingleton方式的id值相同;」
「AddScope方式兩次請(qǐng)求之間不同,但同一請(qǐng)求內(nèi)是相同的;」
「AddTransient方式在同一請(qǐng)求內(nèi)的多次注入間都不相同?!?/strong>
3.7小結(jié)
通過(guò)上述的代碼示例,也證實(shí)了之前的三種方式對(duì)應(yīng)不同生命周期的說(shuō)明。
暫時(shí)性(Transient) : 生命周期是每次獲得對(duì)象都是一次新的對(duì)象,每一次都不一樣。
作用域內(nèi)(Scoped) : 生命周期是在每一次請(qǐng)求作用域內(nèi)是同一個(gè)對(duì)象,非作用域內(nèi)則是新的對(duì)象。
單例(Singletion) : 生命周期是這個(gè)服務(wù)啟動(dòng)后都是一個(gè)對(duì)象,也即是全局單例對(duì)象。
四、其他Ioc容器
通過(guò)上述的了解,我們知道IoC容器是一個(gè)依賴注入框架,在.NET Core中也提供了內(nèi)置的IoC容器,通過(guò)AddXXX方法來(lái)實(shí)例依賴對(duì)象,而在實(shí)際開(kāi)發(fā)中,就是每一個(gè)實(shí)例都需要一個(gè)個(gè)的添加,這樣的實(shí)現(xiàn)方式在小項(xiàng)目中還好,但是如果在復(fù)雜大型的項(xiàng)目中,就略向麻煩些,可能需要添加很多方法來(lái)實(shí)現(xiàn),整體的可觀性也不好。
為了達(dá)到可以簡(jiǎn)化我們工作量,應(yīng)該采用批量注冊(cè),因此我們也可以引入其他的Ioc容器框架,實(shí)現(xiàn)更多的功能和擴(kuò)展。
在平時(shí)開(kāi)發(fā)中,常用的IoC框架有很多,而在這里我們選擇用Autofac,這也是在.net下比較流行的,其他的框架不做說(shuō)明,可自行查閱了解。
「ASP.Net Core中使用Autofac 框架注入 (在后續(xù)篇章會(huì)具體說(shuō)明)」
五、思考
在實(shí)際的開(kāi)發(fā)中,對(duì)于這幾種生命周期,我們應(yīng)該如何應(yīng)用呢?
比如,在像DBContext這種實(shí)例,在實(shí)際開(kāi)發(fā)中是用Transient 還是Scoped呢?
?「建議使用Scoped」
?因?yàn)橛行?duì)象在請(qǐng)求中可以需要用到多個(gè)方法或者多個(gè)Service、Repository的時(shí)候,為了減少實(shí)例初始化的消耗,實(shí)現(xiàn)事務(wù)功能,可以在整個(gè)請(qǐng)求的生命周期共用一個(gè)Scope。
其他的思考:
ASP.NET Core中,默認(rèn)采用了構(gòu)造函數(shù)注入的方式,如果采用屬性注入或者方法注入,又該怎么實(shí)現(xiàn)?
一個(gè)接口多種實(shí)現(xiàn)的時(shí)候,我們將多種實(shí)現(xiàn)都給注入進(jìn)了依賴注入容器中,但是在服務(wù)調(diào)用的時(shí)候總是獲取到最后注入的那個(gè)方法的實(shí)現(xiàn),這時(shí)候就在想能不能實(shí)現(xiàn)動(dòng)態(tài)的選擇使用哪種實(shí)現(xiàn)呢?
一個(gè)作用域(Scoped)服務(wù)中注入一個(gè)瞬時(shí)(Transient)服務(wù)時(shí),瞬時(shí)服務(wù)中的值還會(huì)每次都變化嗎?
鏈?zhǔn)阶⑷霑r(shí),生存期的選擇,三種注入方式的權(quán)重問(wèn)題
以上的思考,大家可以說(shuō)說(shuō)自己的想法。
「在后續(xù)的篇章中,也會(huì)對(duì)這些問(wèn)題,進(jìn)行深入討論說(shuō)明?!?/strong>
六、總結(jié)
本篇主要介紹了什么是IoC容器,了解到它是DI構(gòu)造函注入的框架,它管理著依賴項(xiàng)的生命周期以及映射關(guān)系,同時(shí)也介紹實(shí)踐了在ASP.Net Core中,默認(rèn)提供的內(nèi)置IoC容器,以及它的實(shí)例注冊(cè)方式和相應(yīng)的生命周期。
好啦,這篇文章就先講述到這里吧,「在后續(xù)篇章中會(huì)對(duì)ASP.Net Core中使用Autofac 框架實(shí)踐說(shuō)明」,希望對(duì)大家有所幫助。
如果有不對(duì)的或不理解的地方,希望大家可以多多指正,提出問(wèn)題,一起討論,不斷學(xué)習(xí),共同進(jìn)步。????
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core依赖注入初识与思考的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .NET 异步,你也许不知道的5种用法
- 下一篇: 在asp.net core中使用的验证框