Autofac 官網文檔地址:
https://autofaccn.readthedocs.io/zh/latest/index.html
本文主要講述如何使用 Autofac 框架完成依賴注入等操作,不涉及理論。
Autofac 的使用
我們在 .NET Core 控制台程序中進行測試和實踐。
1,簡單的實踐
首先我們添加一個接口以及實現:
public interface IMyService { }
public class MyService : IMyService { }
然后在 Main 方法中注冊以及構建容器:
class Program
{
private static IContainer Container;
static void Main(string[] args)
{
// 創建容器構建器
var builder = new ContainerBuilder();
// 注冊組件
builder.RegisterType<MyService>().As<IMyService>();
// ...
// 構建容器
Container = builder.Build();
}
}
我們則可以這樣使用:
public static void Test()
{
// 生命周期管理
using (ILifetimeScope scope = Container.BeginLifetimeScope())
{
// 獲取實例
IMyService myService = scope.Resolve<IMyService>();
}
}
.AS()
用於暴露組件的服務。
這就是 Autofac 的簡單使用。
下面我們來討論更詳細的使用方法以及實踐。
2,注冊組件
前面我們通過 ContainerBuilder
對象來注冊組件並且告訴容器有哪些組件暴露了哪些服務。
組件的注冊方式有很多種,前面我們使用了反射的方法去注冊,傳遞一個泛型參數進去:
.RegisterType<MyService>()
或者通過類型(Type)進行注入:
builder.RegisterType(typeof(MyService)).As<IMyService>();
當然,通過反射注冊的組件,它會自動為你注入相應的構造函數。
你也可以通過 UsingConstructor
方法,要求容器實例化組件時,使用哪一個構造函數:
builder.RegisterType<MyComponent>()
.UsingConstructor(typeof(ILogger), typeof(IConfigReader));
我們也可以提前將實例注冊進去:
MyService t = new MyService();
builder.RegisterInstance(t).As<IMyService>();
這樣就會生成一個單例應用。
不過,因為 RegisterInstance(t)
會保留對 t 的引用,也就是說將這個實例注冊到容器的實例中。
當然你可以使用 Lambda 表達式樹來 new :
builder.Register(c => new MyService()).As<IMyService>();
這樣可以避免外部有引用。
如果你不想這樣,可以使用 ExternallyOwned
方法,這樣就會生成一個新的實例到容器中。如果你會 AutoMapper ,這樣會很容易理解。
builder.RegisterInstance(t).As<IMyService>().ExternallyOwned();
3,Lambda 注冊組件
如果一個類型的構造函數依賴於另一個接口,那么這種類型作為組件注冊,就會復雜一些,我們可以使用 Lambda 表達式來注冊組件。
有以下幾個接口和類型:
public interface IA { }
public class A : IA { }
public interface IB { }
public class B : IB
{
private IA _a;
public B(IA a)
{
_a = a;
}
}
那么我們可以先注冊 A 類型,再注冊 B 類型:
builder.RegisterType<A>().As<IA>();
builder.Register(c => new B(c.Resolve<IA>()));
當然,這里使用表達式來介紹方便性。你也可以這樣使用:
builder.RegisterType<A>().As<IA>();
builder.RegisterType<B>().As<IB>();
實例化 B 類型時,會自動為其注入構造函數。
4,注冊泛型
如果要對泛型類型進行注冊:
public interface IA { }
public class A<T> : IA { }
則可以使用 RegisterGeneric
來注冊泛型組件:
builder.RegisterGeneric(typeof(A<>)).As<IA>();
當然,如果 IA 也是泛型的話,應該使用 .As(typeof(IA))
。
5,屬性注入
注冊組件時,使用 PropertiesAutowired
方法,那么容器在生成實例時,會自動注入屬性。
有以下類型:
public interface IA { }
public class A : IA { }
public interface IB { }
public class B : IB
{
public IA A { get; set; }
}
注冊:
builder.RegisterType<A>().As<IA>();
builder.RegisterType<B>().PropertiesAutowired().As<IB>();
那么,容器會自動給 B 類型的屬性注入依賴。
當然,這樣會為類型的每一個屬性注入依賴。
如果我們只是想為某個屬性注入的話,可以這樣 使用 WithProperty
方法,例如:
builder.RegisterType<B>().WithProperty("A",new A()).As<IB>();
6,解析服務
注冊組件后,調用 Build()
方法生成了容器(IContainer)。
然后使用 Resolve
方法在其生命周期內解析服務。
參考前面的示例如下:
using (ILifetimeScope scope = Container.BeginLifetimeScope())
{
// 獲取實例
IMyService myService = scope.Resolve<IMyService>();
}
要注意的是,實例是從生命周期中解析(ILifetimeScope scope),而不是從容器中(IContainer)中解析。
如果想知道一個服務是否已經被注冊,我們可以使用 ResolveOptional()
或 TryResolve()
方法。
using (ILifetimeScope scope = Container.BeginLifetimeScope())
{
IB b;
// 獲取實例
if (scope.TryResolve<IB>(out b))
{
}
}
在解析時,可以傳遞參數,這樣可以控制容器生成實例時,使用能夠構造函數實例化類型。
Autofac提供了多種不同的參數匹配機制:
- NamedParameter - 通過名稱匹配目標參數
- TypedParameter - 通過類型匹配目標參數 (需要匹配具體類型)
- ResolvedParameter - 靈活的參數匹配
示例如下:
namespace AutofacTest
{
public interface IA { }
public class A : IA
{
public A(string a, string b) { Console.WriteLine($"a = {a}, b = {b}"); }
}
class Program
{
private static IContainer Container;
static void Main(string[] args)
{
// 創建容器構建器
var builder = new ContainerBuilder();
builder.RegisterType<A>().As<IA>();
// 構建容器
Container = builder.Build();
Test();
}
public static void Test()
{
// 生命周期管理
using (ILifetimeScope scope = Container.BeginLifetimeScope())
{
IA b = scope.Resolve<IA>(new NamedParameter("a", "測試"), new NamedParameter("b", "測試"));
}
}
}
或者改成:
IA b = scope.Resolve<IA>(new TypedParameter(typeof(string), "測試"), new TypedParameter(typeof(string), "測試"));
另外,Autofac 還支持多種關系的服務解析,其種類如下:
- 直接依賴 (B)
- 延遲實例化 (Lazy)
- 可控生命周期 (Owned)
- 動態實例化 (Func)
- 帶參數實例化 (Func)
- 可遍歷型 (IEnumerable, IList, ICollection)
- 元數據審查(Metadata Interrogation (Meta, Meta))
- 鍵控服務的查找(Keyed Service Lookup (IIndex))
7,生命周期
關於生命周期,你可以參考:https://autofaccn.readthedocs.io/zh/latest/lifetime/index.html
前面我們看到,要獲取實例,使用了
using (ILifetimeScope scope = Container.BeginLifetimeScope())
{
}
BeginLifetimeScope
創建一個生命周期作用域,生命周期的作用域是可釋放的並且可以追蹤組件的釋放。
你可以使用 Dispose()
或者 using{}
形式進行生命周期釋放。
你也可以:
using (ILifetimeScope scope = Container.BeginLifetimeScope())
{
using (ILifetimeScope sc = scope.BeginLifetimeScope())
{
}
}
8,實例作用域
實例的作用域決定了對於暴露出來的同一個服務的實例如何在多個請求之間共享。組件的作用域是在注冊組件是決定的,然后顯式地調用 Resolve()
返回地示例,就會出現具體的行為(單例等)。
8.1 一個依賴一個實例
在 .NET 默認的依賴注入框架中,稱為 'transient
或 factory
,對於每個請求,每次返回的都是不一樣的實例。Autofac 默認就是這種模式。
你也可以使用 InstancePerDependency
顯式聲明:
builder.RegisterType<Worker>().InstancePerDependency();
8.2 單一實例
SingleInstance
方法可以注冊組件為單一實例:
builder.RegisterType<Worker>().SingleInstance();
8.3 生命周期作用域實例
使用 InstancePerLifetimeScope
可以設置組件在一個生命周期作用域內,獲取到的實例都是同一個。
另外,層疊的生命周期作用域也是不同的,例如下面的示例中,結果是 True,False
。
using (ILifetimeScope scope = Container.BeginLifetimeScope())
{
IA b = scope.Resolve<IA>();
IA bb = scope.Resolve<IA>();
Console.WriteLine(b == bb);
using (ILifetimeScope sc = scope.BeginLifetimeScope())
{
IA bbb = sc.Resolve<IA>();
Console.WriteLine(b == bbb);
}
}
另外 Autofac 還有其它方法的作用域管理,請點擊鏈接了解: https://autofaccn.readthedocs.io/zh/latest/lifetime/instance-scope.html
9,Autofac 其它需要學習的知識
Autofac 是非常厲害的框架,本文只是挑入門基礎部分講解,其它自由度高一些的復雜一些的知識點例如:
需要查看文檔學習,這里不再贅述。
ASP.NET Core
ASP.NET Core 中,2.x 和 3.x 的差異比較多,這里只以 3.x 作為示例。
1,默認依賴注入
ASP.NET Core 中,默認的依賴注入,可以使用 ConfigureServices
方法,在此方法中注冊即可。
例如:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IA, A>();
}
2,使用 Autofac
如果要在 ASP.NET Core 中使用 Autofac 作為依賴注入容器,則還需要安裝名為 Microsoft.Extensions.DependencyInjection.Abstractions
的 Nuget 包。
然后在 Program 的 Host 中加上
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
示例如下:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
然后在 Startup 類中,加上 ConfigureContainer
方法,然后在此方法中注冊需要的組件:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<A>().As<IA>();
}
最后在 ConfiguraServices
方法中添加:
services.AddOptions();
即可使用 Autofac 作為 ASP.NET Core 依賴注入容器。
完整代碼:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddControllers();
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<A>().As<IA>();
}
ABP
首先要求你添加了一個 ASP.NET Core 程序,然后配置 ABP ,引入相應的包。可以參考 https://docs.abp.io/zh-Hans/abp/latest/Getting-Started-AspNetCore-Application
ABP 中,默認也是使用 ConfigureServices
直接注入即可,使用示例:
public class AppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient<IA, A>();
}
}
context.Services
即為 IServiceCollection
對象。
當然,ABP 也可以使用 Autofac 作為依賴注入容器。
ABP 中要使用 Autofac,需要引用 Volo.Abp.Autofac
包。
然后在模塊中加上 [DependsOn(typeof(AbpAutofacModule))]
特性。
[DependsOn(typeof(AbpAutofacModule))]
public class AppModule : AbpModule{}
然后在 Startup 中的 ConfiguraServices
方法中,添加 ABP 模塊, 並且設置使用 Autofac。
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<BasicAspNetCoreApplication.AppModule>(options=>
{
options.UseAutofac();
});
}