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