什麼情況下需要引入第三方容器組件?
實際上是大部分情況默認的容器組件是夠我們使用的,但是當我們需要一些非常特殊的場景時,如:
- 基於名稱的注入:把一個服務按照名稱來區分它不同的實現的時候
- 屬性注入:我們的注入方式有FromService的方式、還有構造函數入參的方式,但是在開源社區我們會有很多這種屬性注入的方式,也就是說我們可以直接把服務註冊到某一個類的屬性裏面去而不需要定義構造函數
- 子容器:可以理解成之前講過的Scope,但實際上我們還可以用第三方的框架來去實現些特殊的子容器
- 基於動態代理的AOP:當我們需要在服務中注入我們額外的行爲的時候,我們可以用動態代理的能力
.NET Core的依賴注入框架的核心擴展點是:IServiceProviderFactory
public interface IServiceProviderFactory<TContainerBuilder>
第三方的依賴注入容器都是使用了這個類來作爲擴展點,把自己注入到我們整個的框架裏面來,也就是說我們在使用這些依賴注入框架的時候,我們不需要關注說:誰家的特性、誰家的接口是什麼樣子的,我們只需要使用官方核心的定義就可以了,我們不需要直接依賴這些框架;
public interface IMyService{
void ShowCode();
}
public class MyService: IMyService{
public void ShowCode(){
Console.WriteLine($"MyService.ShowCode:{GetHashCode()}");
}
}
public class MyServiceV2: IMyService{
public MyNameService NameService{get; set;}
public void ShowCode(){
Console.WriteLine($"MyServiceV2.ShowCode:{GetHashCode()}, NameService是否爲空:{NameService == null}");
}
}
public class MyNameService{}
引入Autofac包:
- Autofac.Extensions.DependencyInjection
- Autofac.Extras.DynamicProxy
在 入口文件 中加入:
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
的作用:註冊第三方容器的入口
在 Startup.cs 中添加一個 ConfigureContainer方法:
public void ConfigureContainer(ContainerBuilder builder){
// 一般的寫法:先註冊具體的實現,然後再告訴它我們想把它標記哪個服務的類型,與之前自帶注入的寫法是相反的
builder.RegisterType<MyService>().As<IMyService>();
// 命名註冊:當我們需要把一個服務註冊多次,並且用不同的命名來作爲區分的時候,我們可以用這種方式
builder.RegisterType<MyServiceV2>().Named<IMyService>("service2");
}
上述代碼中的 命名註冊,是如何去使用它的呢?在 Starup.cs 中:
public ILifetimeScope AutofacContainer{get; private set;} // 這裏是把跟容器註冊進來
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
// Autofac容器獲取實例的方式是一組Resolve的方法, ResolveNamed 和 ResolveKey 作用相同,用法不同而已;
var service = this.AutofacContainer.ResolveNamed<IMyService>("service2");
service.ShowCode(); // 這裏輸出的是 MyServiceV2
var servicenamed = this.AutofacContainer.Resolve<IMyService>();
servicenamed.ShowCode(); // 這裏輸出的是 MyService
...
}
運行後輸出:
注意:Autofac容器獲取實例的方式是一組Resolve的方法:
屬性注入
只需要在後面加入 .PropertiesAutowired() 即可:
public void ConfigureContainer(ContainerBuilder builder)
{
//// 一般的寫法:先註冊具體的實現,然後再告訴它我們想把它標記哪個服務的類型
//builder.RegisterType<MyService>().As<IMyService>();
//// 命名註冊:當我們需要把一個服務註冊多次,並且用不同的命名來作爲區分的時候,我們可以用這種方式
//builder.RegisterType<MyServiceV2>().Named<IMyService>("service2");
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired();
}
public ILifetimeScope AutofacContainer{get; private set;} // 這裏是把根容器註冊進來
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
// Autofac容器獲取實例的方式是一組Resolve的方法
//var service = this.AutofacContainer.ResolveNamed<IMyService>("service2");
//service.ShowCode(); // 這裏輸出的是 MyServiceV2
var servicenamed = this.AutofacContainer.Resolve<IMyService>();
servicenamed.ShowCode(); // 這裏輸出的是 MyService
...
}
NameService爲空,說明 NameService 屬性沒有注入進來
我們需要將 MyNameService 也注入進容器中:
public void ConfigureContainer(ContainerBuilder builder)
{
//// 一般的寫法:先註冊具體的實現,然後再告訴它我們想把它標記哪個服務的類型
//builder.RegisterType<MyService>().As<IMyService>();
//// 命名註冊:當我們需要把一個服務註冊多次,並且用不同的命名來作爲區分的時候,我們可以用這種方式
//builder.RegisterType<MyServiceV2>().Named<IMyService>("service2");
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired();
builder.RegisterType<MyNameService>();
}
發現 MyNameService 已經不爲空了,說明 NameService 已經有實例了,屬性注入已經成功了
AOP
我們在不期望改變原有類的情況下,在方法執行時嵌入一些邏輯,讓我們可以在方法執行的切面上任意的插入我們的邏輯
public class MyInterceptor: IInterceptor // 類必須要繼承至 IInterceptor,並且要實現 Intercept方法
{
public void Intercept(IInvocation invocation)
{
System.Console.WriteLine($"Intercept before,Method:{invocation.Method.Name}");
invocation.Proceed();
System.Console.WriteLine($"Intercept after,Method:{invocation.Method.Name}");
}
}
public void ConfigureContainer(ContainerBuilder builder)
{
// 首先我們需要把我們的攔截器MyInterceptor註冊到容器裏面去
builder.RegisterType<MyInterceptor>();
// 然後我們把MyServiceV2註冊進去,並且允許它屬性註冊,開啓攔截器需要我們使用InterceptedBy這個方法,並且把我們的類型註冊進去,最後還要執行一個開關讓我們允許接口攔截器
// 攔截器分兩種類型,一種是接口類型的接口攔截器(常用)和類攔截器,當我們的服務的類型是接口的時候就需要用接口攔截器類型;而如果我們沒有基於接口設計我們的類,而是去實現了類的話,我們就需要用類攔截器,類攔截器需要我們把方法設計爲虛方法,這樣子允許繼承類重載的情況下,纔可以攔截到我們的具體方法
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired().InterceptedBy(typeof(MyInterceptor)).EnableInterfaceInterceptors();
}
public ILifetimeScope AutofacContainer{get; private set;} // 這裏是把根容器註冊進來
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
var servicenamed = this.AutofacContainer.Resolve<IMyService>();
servicenamed.ShowCode(); // 這裏輸出的是 MyService
...
}
子容器
我們可以創建Scope來作爲子容器;
下面代碼中,我們實際上是可以給子容器進行命名,Autofac具備給子容器進行命名的特性;
#region 子容器
這裏我們可以把一個服務注入到子容器中,並且是特定的命名的子容器,那就意味着我們在其他的子容器裏面是獲取不到這個對象的;
builder.RegisterType<MyNameService>().InstancePerMatchingLifetimeScope("myscope");
#endregion
public ILifetimeScope AutofacContainer { get; private set; } // 這裏是把跟容器註冊進來
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
#region 子容器
using(var myscope = AutofacContainer.BeginLifetimeScope("myscope"))
{
var service0 = myscope.Resolve<MyNameService>();
using (var scope = myscope.BeginLifetimeScope())
{
var service1 = scope.Resolve<MyNameService>();
var service2 = scope.Resolve<MyNameService>();
Console.WriteLine($"service1=service2:{service1 == service2}");
Console.WriteLine($"service1=service0:{service1 == service0}");
}
}
#endregion
...
}