Autofac框架初识与应用
一、前言
這上一篇中,主要講述了什么是IoC容器,以及了解到它是DI構(gòu)造函注入的框架,它管理著依賴項(xiàng)的生命周期以及映射關(guān)系,同時也介紹實(shí)踐了在ASP.Net Core中,默認(rèn)提供的內(nèi)置IoC容器,以及它的實(shí)例注冊方式和相應(yīng)的生命周期。
但考慮到在實(shí)際項(xiàng)目中,如果需要一個個添加實(shí)例,會略顯麻煩,為了達(dá)到可以簡化我們工作量,因此我們也可以引入其他的Ioc容器框架,實(shí)現(xiàn)更多的功能和擴(kuò)展。
這里選擇用Autofac,這也是在.net下比較流行的,其他的框架不做說明,可自行查閱了解。
二、說明
AutoFac是一個開源的輕量級的依賴注入容器,也是.net下比較流行的實(shí)現(xiàn)依賴注入的工具之一。
將Autofac整合到你的應(yīng)用的基本流程如下:
按照 控制反轉(zhuǎn) (IoC) 的思想構(gòu)建你的應(yīng)用.
添加Autofac引用.
在應(yīng)用的 startup 處
創(chuàng)建 ContainerBuilder.
注冊組件.
創(chuàng)建容器,將其保存以備后續(xù)使用.
應(yīng)用執(zhí)行階段
從容器中創(chuàng)建一個生命周期.
在此生命周期作用域內(nèi)解析組件實(shí)例.
三、開始
3.1 默認(rèn)容器
在上一篇中定義的三個接口,分別測試Singleton,Scope,Transient三種,一個 TestService服務(wù),
在內(nèi)置的IoC容器中,在Startup.cs類文件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.2 Autofac框架
現(xiàn)在我們使用其他的IoC容器框架來替換默認(rèn)的內(nèi)置IoC,這里選擇使用Autofac框架
「.net core 2.x和3.x 使用autofac注入方式不一樣,此文章是針對.net core 3.x」
首先,我們需要從nuget引用相關(guān)的包.
「Autofac.Extensions.DependencyInjection」(這個包擴(kuò)展了一些微軟提供服務(wù)的類.來方便替換autofac)
然后在Program.cs 新增一行代碼
????????public?static?IHostBuilder?CreateHostBuilder(string[]?args){//var?assemblyName?=?typeof(Startup).GetTypeInfo().Assembly.FullName;return?Host.CreateDefaultBuilder(args).UseServiceProviderFactory(new?AutofacServiceProviderFactory())??//設(shè)置工廠來替換實(shí)例.ConfigureWebHostDefaults(webBuilder?=>{webBuilder.UseStartup<Startup>();});}UseServiceProviderFactory 設(shè)置工廠來替換實(shí)例。
然后在Startup類增加ConfigureContainer方法,在方法中注入依賴:
????public?void?ConfigureContainer(ContainerBuilder?builder){//?Register?your?own?things?directly?with?Autofac,?like:builder.RegisterType<TransientService>().As<ITransientService>();builder.RegisterType<SingletonService>().As<ISingletonService>().SingleInstance();builder.RegisterType<ScopedService>().As<IScopedService>().InstancePerLifetimeScope();builder.RegisterType<TestService>().As<ITestService>().InstancePerLifetimeScope();} ?說明
ASP.NET Core 引入了具有強(qiáng)類型容器配置的能力。 它提供了一個ConfigureContainer方法,您可以使用Autofac單獨(dú)注冊,而不是使用ServiceCollection注冊。
使用ConfigureContainer配置
在配置WebHostBuilder的Program.Main方法中,調(diào)用AddAutofac將Autofac掛鉤到啟動管道中。
在Startup類的ConfigureServices方法中,使用其他庫提供的擴(kuò)展方法將內(nèi)容注冊到IServiceCollection中。
在Startup類的ConfigureContainer方法中,將內(nèi)容直接注冊到AutofacContainerBuilder中。
3.3 測試
啟動運(yùn)行項(xiàng)目,訪問接口/Test
效果如下:
對比之前「默認(rèn)容器」可以發(fā)現(xiàn),在兩次的請求訪問都一樣,可以得到了 4個Transient實(shí)例,2個Scope實(shí)例,1個Singleton實(shí)例。
四、說明
下面主要針對Autofac中的注冊組件、解析服務(wù)兩大步驟,以及其中容器中對應(yīng)實(shí)例的生命周期,進(jìn)行說明。
4.1 注冊組件
通過創(chuàng)建 ContainerBuilder 來注冊組件,并且告訴容器哪些組件,暴露了哪些服務(wù)。
使用 Register() 方法來注冊實(shí)現(xiàn):
「ContainerBuilder 包含一組 Register() 注冊方法,而組件暴露服務(wù),可用使用 ContainerBuilder 上的 As() 方法。」
?即在容器初始化時候,向容器組件添加對象的操作過程。
?通過梳理Autofac所有可用的注冊組件方法,顯示如下圖展示的流程圖。
這里我們只說明下幾種便捷的注冊方法
4.1.1 反射注冊
直接注冊的組件必須是具體的類型,并可用暴露抽象和接口作為服務(wù),但不能注冊一個抽象和接口組件。
使用RegisterType<T>()或者RegisterType(typeof(T))方法:
builder.RegisterType<TestService>().As<ITestService>(); //?或者 builder.RegisterType(typeof(TestService)).As(typeof(ITestService)) ?在多個構(gòu)造函數(shù)時,如果需要,也可手動指定一個構(gòu)造函數(shù)。
使用 UsingConstructor 方法和構(gòu)造方法中代表參數(shù)類型的類型。
??builder.RegisterType<TestService>().UsingConstructor(typeof(TransientService),?typeof(SingletonService)); ?4.1.2 實(shí)例注冊
提前生成對象的實(shí)例并加入容器,以供注冊組件時使用。
使用RegisterInstance()方法
//?new出一個對象注冊: var?output?=?new?StringWriter(); builder.RegisterInstance(output).As<TestService>();如果單例中存在實(shí)例且需要在容器中被組件使用時,
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();4.1.3 Lambda表達(dá)式注冊
當(dāng)組件創(chuàng)建不再是簡單調(diào)用構(gòu)造方法時,可用利用lambda表達(dá)式來實(shí)現(xiàn)一些常規(guī)反射無法實(shí)現(xiàn)的操作。
比如一些復(fù)雜參數(shù)注冊,參數(shù)注入,以及選擇參數(shù)值實(shí)現(xiàn)等。
??builder.Register(x?=>?new?TransientService()).As<ITransientService>(); //?或者指定參數(shù)builder.Register(x?=>?new?TestService(x.Resolve<ITransientService>(),?x.Resolve<IScopedService>(),?x.Resolve<ISingletonService>())).As<ITestService>().InstancePerLifetimeScope();4.1.4 泛型注冊
支持泛型注冊操作,使用 RegisterGeneric() 方法:
builder.RegisterGeneric(typeof(NHibernateRepository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();4.1.5 條件注冊
在一些特殊場景,可能需要通過加上判斷條件,來決定是否執(zhí)行該條注冊語句。
兩種方法:
OnlyIf() - 提供一個表達(dá)式, 表示只有滿足條件,才會執(zhí)行語句。
IfNotRegistered() - 表示沒有其他服務(wù)注冊的情況下,就執(zhí)行語句。
方法在 ContainerBuilder.Build() 時執(zhí)行并且以實(shí)際組件注冊的順序執(zhí)行。
builder.RegisterType<ServiceA>().As<IService>(); builder.RegisterType<ServiceB>().As<IService>().IfNotRegistered(typeof(IService));4.1.6 屬性注入
構(gòu)造方法參數(shù)注入是一種傳值給組件的首選的方法。
?在構(gòu)造函數(shù)中是直接使用服務(wù)類型作為參數(shù),然后AutoFac解析該類時,就會去容器內(nèi)部已存在的組件中查找,然后將匹配的對象注入到構(gòu)造函數(shù)中去。
?但你同樣也可以使用屬性方法注入來傳值。
?是將容器內(nèi)對應(yīng)的組件直接注入到類內(nèi)的屬性中去,在注冊該屬性所屬類的時候,需要使用PropertiesAutowired()方法額外標(biāo)注。
?「這里不討論屬性注入的好壞,也不做說明服務(wù)層屬性怎么注入,只討論說明控制器中屬性如何實(shí)現(xiàn)注入」
注冊組件方法,并使用屬性注入PropertiesAutowired()標(biāo)注。
在控制器中使用屬性來接收, 「其中注入屬性必須標(biāo)注為public」
運(yùn)行測試,發(fā)現(xiàn)如下
發(fā)現(xiàn)_transientService為null,所以根本沒有注入成功。
「這是因?yàn)榭刂破鞅旧淼膶?shí)例(以及它的處理)是由框架創(chuàng)建和擁有的,而不是由容器所有」
因此我們需要改變控制器本身的創(chuàng)建及其擁有。
在Startup.cs中修改ConfigureServices方法,替換從IServiceProvider中解析控制器實(shí)例的所有者。
注意,替換的方法一定要在AddControllers之前。
在ContainerBuilder中通過注冊控制器,并使用屬性注入功能實(shí)現(xiàn).
這樣就可以在Controller中進(jìn)行屬性注入了;
再次運(yùn)行查看,發(fā)現(xiàn)已經(jīng)成功注入了。
4.1.7 程序集注冊
當(dāng)我們需要實(shí)現(xiàn)批量注冊的時候,也可以使用程序集的方式來注冊,這也是常用的方法。
?可通過指定過濾類型,服務(wù),掃描模塊等方式來找到需要注冊的組件。
?var?assemblies?=?Assembly.GetExecutingAssembly();builder.RegisterAssemblyTypes(assemblies)//程序集內(nèi)所有具象類? .Where(c?=>?c.Name.EndsWith("Service")) .PublicOnly()//只要public訪問權(quán)限的 .Where(cc?=>?cc.IsClass)//只要class型(主要為了排除值和interface類型)? .AsImplementedInterfaces();//自動以其實(shí)現(xiàn)的所有接口類型暴露(包括IDisposable接口)說明:
RegisterAssemblyTypes() :接收包含一個或多個程序集的數(shù)組作為參數(shù)
RegisterAssemblyModules() : 接收模塊作為參數(shù),進(jìn)行模塊掃描注冊
PublicOnly() :指定公有方法被注冊
Where() :要過濾注冊的類型
Except() :要排除的類型
As() :反射出其實(shí)現(xiàn)的接口
AsImplementedInterfaces() : 自動以其實(shí)現(xiàn)的所有接口類型暴露(包括IDisposable接口)
4.2 暴露服務(wù)
上面提到了注冊組件時, 我們得告訴Autofac, 組件「暴露」了哪些服務(wù)。
在上面注冊實(shí)現(xiàn)中,大部分使用到了As() 方法。
當(dāng)然,Autofac也提供了其他標(biāo)注來暴露服務(wù)的方法。
4.2.1 默認(rèn)暴露自身類型服務(wù)
常用的幾種方法如下:
builder.RegisterType<CallLogger>();//不標(biāo)注,默認(rèn)以自身類型暴露服務(wù) builder.RegisterType<CallLogger>().AsSelf(); builder.RegisterType<CallLogger>().As<CallLogger>(); builder.RegisterType<CallLogger>().As(typeof(CallLogger));4.2.2 多個暴露服務(wù)類型
「以其實(shí)現(xiàn)的接口(interface)暴露服務(wù)」,暴露的類型可以是多個,比如CallLogger類實(shí)現(xiàn)了ILogger接口和ICallInterceptor接口。
?暴露服務(wù)后, 可以解析基于該服務(wù)的組件了. 但請注意, 一旦將組件暴露為一個特定的服務(wù), 默認(rèn)的服務(wù) (組件類型) 將被覆蓋。
?所以,為了防止被其他服務(wù)覆蓋,可以使用 AsSelf() 方法。
Copybuilder.RegisterType<CallLogger>().As<ILogger>().As<ICallInterceptor>().AsSelf();這樣你既可以實(shí)現(xiàn)組件暴露一系列特定的服務(wù), 又可以讓它暴露默認(rèn)的服務(wù)。
4.2.3 程序集注冊指定暴露類型
可通過指定接口類型暴露服務(wù),使用As() 方法
指定所有實(shí)現(xiàn)的接口類型進(jìn)行暴露
使用AsImplementedInterfaces()函數(shù)實(shí)現(xiàn),相當(dāng)于一個類實(shí)現(xiàn)了幾個接口(interface)就會暴露出幾個服務(wù),等價于上面連寫多個As()的作用。
?publi?void?ConfigureContainer(ContainerBuilder?builder) { builder.RegisterAssemblyTypes(asm).Where(t?=>?t.Name.EndsWith("Repository")).AsImplementedInterfaces();//自動以其實(shí)現(xiàn)的所有接口類型暴露(包括IDisposable接口) }4.3 解析服務(wù)
在 注冊完組件并暴露相應(yīng)的服務(wù)后, 可以從創(chuàng)建的容器或其生命周期中解析服務(wù)。
使用 Resolve() 方法來解析實(shí)現(xiàn):
通過梳理Autofac所有可用的解析服務(wù)方法,顯示如下圖展示的流程圖。
在 注冊完組件并暴露相應(yīng)的服務(wù)后, 你可以從創(chuàng)建的容器或其子 生命周期 中解析服務(wù). 讓我們使用 Resolve() 方法來實(shí)現(xiàn):
var?builder?=?new?ContainerBuilder(); builder.RegisterType<MyComponent>().As<IService>(); var?container?=?builder.Build();using(var?scope?=?container.BeginLifetimeScope()) {var?service?=?scope.Resolve<IService>(); }4.3.1 解析時傳參
當(dāng)解析服務(wù)時, 需要傳參,可以使用Resolve() 方法來接受可變長度的參數(shù)。
可用參數(shù)類型
NamedParameter - 通過名稱匹配目標(biāo)參數(shù)
TypedParameter - 通過類型匹配目標(biāo)參數(shù) (需要匹配具體類型)
ResolvedParameter - 靈活的參數(shù)匹配
?反射組件的參數(shù)
Lambda表達(dá)式組件的參數(shù)
不顯式調(diào)用Resolve傳參
4.3.2 隱式關(guān)系類型
?這里不做詳細(xì)說明,詳見官方文檔
?4.4 生命周期
下面講下AutoFac定義的幾種生命周期作用域,并與.NET Core默認(rèn)的生命周期作了簡要的對比。
4.4.1 暫時性
每次在向服務(wù)容器進(jìn)行請求時都會創(chuàng)建新的實(shí)例,相當(dāng)于每次都new出一個。
「注冊方式:」
使用InstancePerDependency()方法標(biāo)注,如果不標(biāo)注,這也是默認(rèn)的選項(xiàng)。以下兩種注冊方法是等效的:
//不指定,默認(rèn)就是瞬時的 builder.RegisterType<TransientService>().As<ITransientService>();//指定其生命周期域?yàn)樗矔r builder.RegisterType<TransientService>().As<ITransientService>().InstancePerDependency();「對比」:
與默認(rèn)的容器中自帶的生命周期AddTransient相同,也是每次都是全新的實(shí)例。 使用AddTransient()注冊:
?services.AddTransient<ITransientService,?TransientService>()4.4.2 作用域內(nèi)
在每次Web請求時被創(chuàng)建一次實(shí)例,生命周期橫貫整次請求。即在每個生命周期作用域內(nèi)是單例的。
「注冊方式:」
使用InstancePerLifetimeScope()方法標(biāo)識:
builder.RegisterType<ScopedService>().As<IScopedService>().InstancePerLifetimeScope();「對比」
與默認(rèn)的容器中自帶的生命周期AddScoped相同,.NET Core框架自帶的容器全權(quán)接管了請求和生命周期作用域的創(chuàng)建,使用Scoped()可以實(shí)現(xiàn)相同的效果。 使用AddScoped()注冊:
?services.AddScoped<IScopedService,?ScopedService>();4.4.3 匹配作用域內(nèi)
即每個匹配的生命周期作用域一個實(shí)例。 該類型其實(shí)是上面的“作用域內(nèi)”的其中一種,可以對實(shí)例的共享有更加精準(zhǔn)的控制.。我們通過允許給域“打標(biāo)簽”,只要在這個特定的標(biāo)簽域內(nèi)就是單例的。
注冊 使用InstancePerMatchingLifetimeScope(string tagName)方法注冊:
當(dāng)你開始一個生命周期時, 提供的標(biāo)簽值和它就關(guān)聯(lián)起來了。
解析
如果你嘗試從一個名稱并不匹配的生命周期中解析一個每個匹配生命周期作用域的組件你會得到一個異常!
?4.4.4 全局單例
即全局只有一個實(shí)例,即每一個后續(xù)請求都使用同一個實(shí)例。
「注冊方式:」
使用SingleInstance()方法標(biāo)識:
?builder.RegisterType<SingletonService>().As<ISingletonService>().SingleInstance()「對比」
與默認(rèn)的容器中自帶的生命周期AddSingleton相同。 使用AddSingleton();注冊:
services.AddSingleton<ISingletonService,?SingletonService>(); ?還有其他生命周期補(bǔ)充:
每個請求一個實(shí)例(Instance Per Request) : 其實(shí)是一種的“匹配作用域內(nèi)單例”的一種。
每次被擁有一個實(shí)例(Instance Per Owned)
線程作用域(Thread Scope)
這幾種在這不做詳細(xì)說明,具體可以查看官網(wǎng)。
?4.5 小結(jié)
在.NET Core中默認(rèn)的容器自帶生命周期只有3種類型,而相比于autofac,其顯得更加豐富復(fù)雜些。
五、總結(jié)
本篇主要介紹autofac框架的使用,從注冊組件,到暴露服務(wù),及解析服務(wù)的各個過程。
同時也與.NET Core框架默認(rèn)容器相比,更加豐富了一些注冊方法和更復(fù)雜的生命周期,在應(yīng)用上,也更加輕巧快捷,特別是在批量注冊上更顯實(shí)用。
文章展示的思維導(dǎo)圖
?https://www.processon.com/view/link/6072ed8863768912ae50b483
?參考資料:Autofac官方網(wǎng)站:
?https://autofaccn.readthedocs.io/en/latest/getting-started/index.html
?好啦,這篇文章就先講述到這里吧,希望對大家有所幫助。
如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學(xué)習(xí),共同進(jìn)步。????
總結(jié)
以上是生活随笔為你收集整理的Autofac框架初识与应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 Redis Stream 实现消息
- 下一篇: 聊一聊数据导出那些事