一、概念解釋
使用 .NET,通過(guò) new 運(yùn)算符(即,new MyService 或任何想要實(shí)例化的對(duì)象類型)調(diào)用構(gòu)造函數(shù)即可輕松實(shí)現(xiàn)對(duì)象實(shí)例化。這樣就形成了強(qiáng)耦合,所以提供了一個(gè)間接層,不用直接使用 new 運(yùn)算符實(shí)例化服務(wù),而是請(qǐng)求一個(gè)接口,并提供程序?qū)崿F(xiàn)該接口。我們將解耦返回到客戶端的實(shí)際實(shí)例的模式稱為控制反轉(zhuǎn)。
1、控制反轉(zhuǎn)
IOC—Inversion of Control,即“控制反轉(zhuǎn)”,不是什么技術(shù),而是一種設(shè)計(jì)思想。Ioc意味著將你設(shè)計(jì)好的對(duì)象交給容器控制,而不是傳統(tǒng)的在你的對(duì)象內(nèi)部直接控制。
●誰(shuí)控制誰(shuí),控制什么:傳統(tǒng)程序設(shè)計(jì),我們直接在對(duì)象內(nèi)部通過(guò)new進(jìn)行創(chuàng)建對(duì)象,是程序主動(dòng)去創(chuàng)建依賴對(duì)象;而IoC是有專門(mén)一個(gè)容器來(lái)創(chuàng)建這些對(duì)象,即由Ioc容器來(lái)控制對(duì)象的創(chuàng)建;誰(shuí)控制誰(shuí)?當(dāng)然是IoC 容器控制了對(duì)象;控制什么?那就是主要控制了外部資源獲?。ú恢皇菍?duì)象也包括比如文件等)。
●為何是反轉(zhuǎn),哪些方面反轉(zhuǎn)了:有反轉(zhuǎn)就有正轉(zhuǎn),傳統(tǒng)應(yīng)用程序是由我們自己在對(duì)象中主動(dòng)控制去直接獲取依賴對(duì)象(通過(guò)new進(jìn)行創(chuàng)建對(duì)象),也就是正轉(zhuǎn);而反轉(zhuǎn)則是由容器來(lái)幫忙創(chuàng)建及注入依賴對(duì)象;為何是反轉(zhuǎn)?因?yàn)橛扇萜鲙臀覀儾檎壹白⑷胍蕾噷?duì)象,對(duì)象只是被動(dòng)的接受依賴對(duì)象,所以是反轉(zhuǎn);哪些方面反轉(zhuǎn)了?依賴對(duì)象的獲取被反轉(zhuǎn)了。
●容器:容器負(fù)責(zé)兩件事情:
- 綁定服務(wù)與實(shí)例之間的關(guān)系
- 獲取實(shí)例,并對(duì)實(shí)例進(jìn)行管理(創(chuàng)建與銷毀)
2、依賴注入
DI—Dependency Injection,即“依賴注入”:是組件之間依賴關(guān)系由容器在運(yùn)行期決定,形象的說(shuō),即由容器動(dòng)態(tài)的將某個(gè)依賴關(guān)系注入到組件之中。依賴注入的目的并非為軟件系統(tǒng)帶來(lái)更多功能,而是為了提升組件重用的頻率,并為系統(tǒng)搭建一個(gè)靈活、可擴(kuò)展的平臺(tái)。通過(guò)依賴注入機(jī)制,我們只需要通過(guò)簡(jiǎn)單的配置,而無(wú)需任何代碼就可指定目標(biāo)需要的資源,完成自身的業(yè)務(wù)邏輯,而不需要關(guān)心具體的資源來(lái)自何處,由誰(shuí)實(shí)現(xiàn)。 理解DI的關(guān)鍵是:“誰(shuí)依賴誰(shuí),為什么需要依賴,誰(shuí)注入誰(shuí),注入了什么”,那我們來(lái)深入分析一下:
●誰(shuí)依賴于誰(shuí):當(dāng)然是應(yīng)用程序依賴于IoC容器;
●為什么需要依賴:應(yīng)用程序需要IoC容器來(lái)提供對(duì)象需要的外部資源;
●誰(shuí)注入誰(shuí):很明顯是IoC容器注入應(yīng)用程序某個(gè)對(duì)象,應(yīng)用程序依賴的對(duì)象;
●注入了什么:就是注入某個(gè)對(duì)象所需要的外部資源(包括對(duì)象、資源、常量數(shù)據(jù))。
3、兩者關(guān)系
控制反轉(zhuǎn)是目的,依賴注入是方式
二、實(shí)現(xiàn)DI
我們?cè)诳刂婆_(tái)應(yīng)用程序中,先自己模擬實(shí)現(xiàn)簡(jiǎn)單的DI,主要步驟如下
1)編寫(xiě)測(cè)試對(duì)象及對(duì)應(yīng)的接口代碼
2)編寫(xiě)容器,實(shí)現(xiàn)對(duì)象注冊(cè)和提取
1、首先,我們編寫(xiě)我們自己的測(cè)試對(duì)象如下
public class Company : ICompany
{
}
編寫(xiě)對(duì)應(yīng)的接口代碼如下:
public interface ICompany
{
}
2、編寫(xiě)容器
2.1 創(chuàng)建線程安全的鍵/值對(duì)集合用來(lái)存儲(chǔ)注冊(cè)的服務(wù)
//線程安全的鍵/值對(duì)集合用來(lái)存儲(chǔ)注冊(cè)的服務(wù)
private ConcurrentDictionary<Type, Type> typeMapping = new ConcurrentDictionary<Type, Type>();
2.2 注冊(cè)
public void Register<T1, T2>()
{
Register(typeof(T1), typeof(T2));
}
public void Register(Type from, Type to)
{
//添加注冊(cè)
typeMapping[from] = to;
}
2.3 根據(jù)程序需要的類型名稱選擇相應(yīng)的實(shí)體類型,并返回類型實(shí)例
public T GetService<T>() where T : class
{
return this.GetService(typeof(T)) as T;
}
//根據(jù)程序需要的類型名稱選擇相應(yīng)的實(shí)體類型,并返回類型實(shí)例
public object GetService(Type serviceType)
{
Type type;
//
if(!typeMapping.TryGetValue(serviceType, out type))
{
type = serviceType;
}
if(type.IsInterface || type.IsAbstract)
{
return null;
}
//根據(jù)構(gòu)造函數(shù)參數(shù)獲取實(shí)例對(duì)象
ConstructorInfo constructor = type.GetConstructors().FirstOrDefault();
if(null == constructor)
{
return null;
}
object service = constructor.Invoke(null);
return service;
}
3、使用
namespace IocDemo
{
class Program
{
static void Main(string[] args)
{
Cat cat = new Cat();
//注冊(cè)
cat.Register<ICompany, Company>();
//獲取
ICompany service = cat.GetService<ICompany>();
Console.WriteLine("cat.GetService<ICompany>(): {0}", service);
Console.ReadKey();
}
}
}
4、構(gòu)造器注入
4.1 構(gòu)造器注入
構(gòu)造器注入就在在構(gòu)造函數(shù)中借助參數(shù)將依賴的對(duì)象注入到創(chuàng)建的對(duì)象之中。如下面的代碼片段所示,Company針對(duì)Organize的依賴體現(xiàn)在只讀屬性O(shè)rganize上,針對(duì)該屬性的初始化實(shí)現(xiàn)在構(gòu)造函數(shù)中,具體的屬性值由構(gòu)造函數(shù)的傳入的參數(shù)提供。當(dāng)DI容器通過(guò)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)Company對(duì)象之前,需要根據(jù)當(dāng)前注冊(cè)的類型匹配關(guān)系以及其他相關(guān)的注入信息創(chuàng)建并初始化參數(shù)對(duì)象。
public class Company : ICompany
{
public IOrganize Organize { get; }
public Company(IOrganize organize)
{
this.Organize = organize;
}
}
//獲取參數(shù)
object[] argments = constructor.GetParameters().Select(p => this.GetService(p.ParameterType)).ToArray();
object service = constructor.Invoke(argments);
4.2 多個(gè)構(gòu)造器
為了解決構(gòu)造函數(shù)的選擇問(wèn)題,我們引入如下這個(gè)InjectionAttribute特性。我們將所有公共實(shí)例構(gòu)造函數(shù)作為候選的構(gòu)造函數(shù),并會(huì)優(yōu)先選擇標(biāo)注了該特性的構(gòu)造函數(shù)。加入GetConstructor()方法獲取構(gòu)造函數(shù),當(dāng)構(gòu)造函數(shù)被選擇出來(lái)后,我們需要通過(guò)GetService()分析其參數(shù)類型來(lái)提供具體的參數(shù)值,這實(shí)際上是一個(gè)遞歸的過(guò)程。
(1)加入InjectionAttribute.cs
namespace IoCDemo.FlowControl
{
[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public class InjectionAttribute : Attribute { }
}
(2)GetService()修改如下
//根據(jù)程序需要的類型名稱選擇相應(yīng)的實(shí)體類型,并返回類型實(shí)例
public object GetService(Type serviceType)
{
Type type;
//
if(!typeMapping.TryGetValue(serviceType, out type))
{
type = serviceType;
}
if(type.IsInterface || type.IsAbstract)
{
return null;
}
//根據(jù)構(gòu)造函數(shù)參數(shù)獲取實(shí)例對(duì)象
ConstructorInfo constructor = this.GetConstructor(type);
if (null == constructor)
{
return null;
}
//獲取參數(shù)
object[] argments = constructor.GetParameters().Select(p => this.GetService(p.ParameterType)).ToArray();
object service = constructor.Invoke(argments);
return service;
}
//根據(jù)標(biāo)注InjectionAttribute特性,獲取構(gòu)造函數(shù),如果沒(méi)有標(biāo)記則返回默認(rèn)的
protected virtual ConstructorInfo GetConstructor(Type type)
{
ConstructorInfo[] constructors = type.GetConstructors();
return constructors.FirstOrDefault(c => c.GetCustomAttribute<InjectionAttribute>() != null)
?? constructors.FirstOrDefault();
}
(3)對(duì)象及接口修改如下
public class Organize : IOrganize
{
}
public interface IOrganize
{
}
public class Company : ICompany
{
public IOrganize Organize { get; private set; }
public Company() { }
[Injection]
public Company(IOrganize organize)
{
this.Organize = organize;
}
}
(4)使用
namespace IocDemo
{
class Program
{
static void Main(string[] args)
{
Cat cat = new Cat();
cat.Register<ICompany, Company>();
cat.Register<IOrganize, Organize>();
ICompany service = cat.GetService<ICompany>();
Console.WriteLine("cat.GetService<ICompany>(): {0}", service);
Console.ReadKey();
}
}
}
5、屬性注入
如果依賴直接體現(xiàn)為類的某個(gè)屬性,并且該屬性不是只讀的,我們可以讓DI容器在對(duì)象創(chuàng)建之后自動(dòng)對(duì)其進(jìn)行賦值進(jìn)而達(dá)到依賴自動(dòng)注入的目的。一般來(lái)說(shuō),我們?cè)诙x這種類型的時(shí)候,需要顯式將這樣的屬性標(biāo)識(shí)為需要自動(dòng)注入的依賴屬性以區(qū)別于該類型的其他普通的屬性。如下面的代碼片段所示,我們通過(guò)標(biāo)注InjectionAttribute特性的方式將屬性O(shè)rganize設(shè)置為自動(dòng)注入的依賴屬性。對(duì)于由DI容器提供的Company對(duì)象,它的Organize屬性將會(huì)自動(dòng)被初始化。
public class Company : ICompany
{
[Injection]
public IOrganize Organize { get; set; }
}
在GetService()中加入InitializeInjectedProperties()來(lái)實(shí)現(xiàn)屬性注入
public object GetService(Type serviceType)
{
/**
省略
**/
object service = constructor.Invoke(argments);
this.InitializeInjectedProperties(service);
return service;
}
protected virtual void InitializeInjectedProperties(object service)
{
PropertyInfo[] properties = service.GetType().GetProperties()
.Where(p => p.CanWrite && p.GetCustomAttribute<InjectionAttribute>() != null)
.ToArray();
Array.ForEach(properties, p => p.SetValue(service, this.GetService(p.PropertyType)));
}
5、方法注入
體現(xiàn)依賴關(guān)系的字段或者屬性可以通過(guò)方法的形式初始化。如下面的代碼片段所示,Company針對(duì)Organize的依賴體現(xiàn)在屬性上,針對(duì)該屬性的初始化實(shí)現(xiàn)在Method方法中,具體的屬性值由構(gòu)造函數(shù)的傳入的參數(shù)提供。我們同樣通過(guò)標(biāo)注特性(InjectionAttribute)的方式將該方法標(biāo)識(shí)為注入方法。DI容器在調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)Foo對(duì)象之后,它會(huì)自動(dòng)調(diào)用這個(gè)Method方法對(duì)只讀屬性O(shè)rganize進(jìn)行賦值。
public class Company : ICompany
{
public IOrganize Organize { get; set; }
[Injection]
public void Method(IOrganize organize)
{
this.Organize = organize;
}
}
在GetService()中加入InvokeInjectedMethods()來(lái)實(shí)現(xiàn)方法注入
public object GetService(Type serviceType)
{
/**
省略
**/
object service = constructor.Invoke(argments);
this.InitializeInjectedProperties(service);
this.InvokeInjectedMethods(service);
return service;
}
protected virtual void InvokeInjectedMethods(object service)
{
MethodInfo[] methods = service.GetType().GetMethods()
.Where(m => m.GetCustomAttribute<InjectionAttribute>() != null)
.ToArray();
Array.ForEach(methods, m =>
{
object[] arguments = m.GetParameters().Select(p => this.GetService(p.ParameterType)).ToArray();
m.Invoke(service, arguments);
});
}
三、.NET Core 自帶DI框架
毫不夸張地說(shuō),整個(gè)ASP.NET Core框架是建立在一個(gè)依賴注入框架之上的,它在應(yīng)用啟動(dòng)時(shí)構(gòu)建請(qǐng)求處理管道過(guò)程中,以及利用該管道處理每個(gè)請(qǐng)求過(guò)程中使用到的服務(wù)對(duì)象均來(lái)源于DI容器。該DI容器不僅為ASP.NET Core框架提供必要的服務(wù),同時(shí)作為了應(yīng)用的服務(wù)提供者,依賴注入已經(jīng)成為了ASP.NET Core應(yīng)用基本的編程模式。使用.NET Core自帶的DI框架,我們要先通過(guò)Nuget安裝Microsoft.Extensions.DependencyInjection。其核心就是IServiceCollection與IServiceProvider兩個(gè)類,我們添加的服務(wù)注冊(cè)被保存到通過(guò)IServiceCollection接口表示的集合之中,包含服務(wù)注冊(cè)信息的IServiceCollection對(duì)象最終被用來(lái)創(chuàng)建作為DI容器的IServiceProvider對(duì)象。當(dāng)需要消費(fèi)某個(gè)服務(wù)實(shí)例的時(shí)候,我們只需要指定服務(wù)類型調(diào)用IServiceProvider的GetService方法,IServiceProvider就會(huì)根據(jù)對(duì)應(yīng)的服務(wù)注冊(cè)提供所需的服務(wù)實(shí)例。

1、服務(wù)的注冊(cè)與消費(fèi)
using Microsoft.Extensions.DependencyInjection;
using System;
namespace 自帶DI
{
interface ITransient { }
class Transient : ITransient { }
interface ISingleton { }
class Singleton : ISingleton { }
interface IScoped { }
class Scoped : IScoped { }
class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services = services.AddTransient<ITransient, Transient>();
services = services.AddScoped<IScoped, Scoped>();
services = services.AddSingleton<ISingleton, Singleton>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
Console.WriteLine(serviceProvider.GetService<ITransient>() is Transient);
Console.WriteLine(serviceProvider.GetService<IScoped>() is Scoped);
Console.WriteLine(serviceProvider.GetService<ISingleton>() is Singleton);
Console.ReadKey();
}
}
}

應(yīng)用初始化過(guò)程中添加的服務(wù)注冊(cè)是DI容器用于提供所需服務(wù)實(shí)例的依據(jù)。由于IServiceProvider總是利用指定的服務(wù)類型來(lái)提供對(duì)應(yīng)服務(wù)實(shí)例,所以服務(wù)是基于類型進(jìn)行注冊(cè)的,我們傾向于利用接口來(lái)對(duì)服務(wù)進(jìn)行抽象,所以這里的服務(wù)類型一般為接口。除了以指定服務(wù)實(shí)例的形式外,我們?cè)谧?cè)服務(wù)的時(shí)候必須指定一個(gè)具體的生命周期模式。
我們創(chuàng)建了一個(gè)ServiceCollection(它是對(duì)IServiceCollection接口的默認(rèn)實(shí)現(xiàn))對(duì)象并調(diào)用相應(yīng)的方法(AddTransient、AddScoped和AddSingleton)針對(duì)接口ITransient、ISingleton和IScoped注冊(cè)了對(duì)應(yīng)的服務(wù),從方法命名可以看出注冊(cè)的服務(wù)采用的生命周期模式分別為T(mén)ransient、Scoped和Singleton。在完成服務(wù)注冊(cè)之后,我們調(diào)用IServiceCollection接口的擴(kuò)展方法BuildServiceProvider創(chuàng)建出代表DI容器的IServiceProvider對(duì)象,并利用它調(diào)用后者的GetService<T>方法來(lái)提供相應(yīng)的服務(wù)實(shí)例。運(yùn)行結(jié)果表明IServiceProvider提供的服務(wù)實(shí)例與預(yù)先添加的服務(wù)注冊(cè)是一致的。
(1)IServiceCollection
public interface IServiceCollection : IList<ServiceDescriptor>
{
}
IServiceCollection對(duì)象是一個(gè)存放服務(wù)注冊(cè)信息的集合
(2)ServiceDescriptor
public class ServiceDescriptor
{
public ServiceLifetime Lifetime { get; }
public Type ServiceType { get; }
public Type ImplementationType { get; }
public object ImplementationInstance { get; }
public Func<IServiceProvider, object> ImplementationFactory { get; }
public ServiceDescriptor(Type serviceType, object instance);
public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}
DI框架將服務(wù)注冊(cè)存儲(chǔ)在一個(gè)通過(guò)IServiceCollection接口表示的集合之中。如上面的代碼片段所示,一個(gè)IServiceCollection對(duì)象本質(zhì)上就是一個(gè)元素類型為ServiceDescriptor的列表。屬性包含生命周期、接口類型、實(shí)現(xiàn)類型、實(shí)現(xiàn)實(shí)例、實(shí)現(xiàn)工廠,后三個(gè)屬性體現(xiàn)了服務(wù)實(shí)例的三種提供方式,并對(duì)應(yīng)著三個(gè)構(gòu)造函數(shù)。
(3)注冊(cè)服務(wù)
我們通過(guò)ServiceCollectionServiceExtensions提供的方法注冊(cè)類型
在第一個(gè)代碼塊中,都是使用“服務(wù)類型實(shí)例類型”(提供一個(gè)服務(wù)類型,一個(gè)實(shí)例類型)的注冊(cè)方式。此外,微軟還提供了“服務(wù)實(shí)例”(提供一個(gè)服務(wù)類型,一個(gè)實(shí)例對(duì)象)以及“服務(wù)實(shí)例工廠”(提供一個(gè)服務(wù)類型,一個(gè)實(shí)例對(duì)象工廠)的注冊(cè)方式,前者只供單例服務(wù)使用,使用起來(lái)也很簡(jiǎn)單
//服務(wù)實(shí)例(提供一個(gè)服務(wù)類型,一個(gè)實(shí)例對(duì)象)
services.AddSingleton<ISingleton>(new Singleton());
//服務(wù)實(shí)例工廠(提供一個(gè)服務(wù)類型,一個(gè)實(shí)例對(duì)象工廠)
services.AddSingleton<ISingleton>(_ => new Singleton());
(4)生命周期
使用 AddSingleton、AddScoped、AddTransient 三種方式注冊(cè)的服務(wù)在 ServiceDescriptor 中的 LifeTime 屬性分別對(duì)應(yīng)下面這個(gè)枚舉類型
public enum ServiceLifetime
{
Singleton,
Scoped,
Transient
}
1、Transient:每次從容器 (IServiceProvider)中獲取的時(shí)候都是一個(gè)新的實(shí)例
2、Singleton:每次從同根容器中(同根 IServiceProvider)獲取的時(shí)候都是同一個(gè)實(shí)例
3、Scoped:每次從同一個(gè)容器中獲取的實(shí)例是相同的
interface ITransient { }
class Transient : ITransient { }
interface ISingleton { }
class Singleton : ISingleton { }
interface IScoped { }
class Scoped : IScoped { }
class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services = services.AddTransient<ITransient, Transient>();
services = services.AddScoped<IScoped, Scoped>();
services = services.AddSingleton<ISingleton, Singleton>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
Console.WriteLine(ReferenceEquals(serviceProvider.GetService<ITransient>(), serviceProvider.GetService<ITransient>()));
Console.WriteLine(ReferenceEquals(serviceProvider.GetService<IScoped>(), serviceProvider.GetService<IScoped>()));
Console.WriteLine(ReferenceEquals(serviceProvider.GetService<ISingleton>(), serviceProvider.GetService<ISingleton>()));
IServiceProvider serviceProvider1 = serviceProvider.CreateScope().ServiceProvider;
IServiceProvider serviceProvider2 = serviceProvider.CreateScope().ServiceProvider;
Console.WriteLine(ReferenceEquals(serviceProvider1.GetService<IScoped>(), serviceProvider1.GetService<IScoped>()));
Console.WriteLine(ReferenceEquals(serviceProvider1.GetService<IScoped>(), serviceProvider2.GetService<IScoped>()));
Console.WriteLine(ReferenceEquals(serviceProvider1.GetService<ISingleton>(), serviceProvider2.GetService<ISingleton>()));
/* False
* True
* True
* True
* False
* True
*/
}
}
(5)IServiceProvider

在這張類圖中,有三個(gè)核心接口,IServiceProvider 表示容器,具備提供對(duì)象功能,IServiceScope 代表一個(gè)具有范圍的節(jié)點(diǎn)容器,而 IServiceProviderEngine 是提供對(duì)象核心接口,具有一個(gè)根容器,每次創(chuàng)建對(duì)象的時(shí)候都會(huì)帶上具有范圍的節(jié)點(diǎn)容器。關(guān)于容器和引擎這兩個(gè)概念如何判斷呢?凡是實(shí)現(xiàn)了 IServiceProvider 的都可以稱為容器,同樣的,凡是實(shí)現(xiàn)了 IServiceProviderEngine 的都可以稱為引擎,引擎仍然是一個(gè)容器,實(shí)際上,上圖中所有的類型都是一個(gè)容器,因?yàn)樗麄兌贾苯踊蛘唛g接實(shí)現(xiàn)了 IServiceProvider 接口,然而最終負(fù)責(zé)創(chuàng)建服務(wù)實(shí)例的必然是一個(gè)引擎。
IServiceScope 的默認(rèn)實(shí)現(xiàn)是 ServiceProviderEngineScope,ResolvedServices 存儲(chǔ)已經(jīng)實(shí)例化過(guò)的生命周期為 Scoped 的對(duì)象,_disposables 存儲(chǔ)通過(guò)該根創(chuàng)建的所有實(shí)例類型(如果該類型實(shí)現(xiàn)了 IDisposable 接口),在調(diào)用 Dispose 方法時(shí)會(huì)釋放 _disposables 中的對(duì)象,同時(shí)清空 ResolvedServices 中的實(shí)例。
ServiceProviderEngineScope 總在引擎被創(chuàng)建的時(shí)候初始化,并且在 GetService 方法中,傳遞的根容器正是這個(gè) Root,所以根容器一般情況不會(huì)更改,然而我們可以 CreateScope 方法(來(lái)自 IServiceScopeFactory 接口)來(lái)創(chuàng)建一個(gè)新的根容器(實(shí)際上是一個(gè)新的節(jié)點(diǎn))

●Singleton:IServiceProvider創(chuàng)建的服務(wù)實(shí)例保存在作為根容器的IServiceProvider上,所有多個(gè)同根的IServiceProvider對(duì)象提供的針對(duì)同一類型的服務(wù)實(shí)例都是同一個(gè)對(duì)象。
●Scoped:IServiceProvider創(chuàng)建的服務(wù)實(shí)例由自己保存,所以同一個(gè)IServiceProvider對(duì)象提供的針對(duì)同一類型的服務(wù)實(shí)例均是同一個(gè)對(duì)象。
●Transient:針對(duì)每一次服務(wù)提供請(qǐng)求,IServiceProvider總是創(chuàng)建一個(gè)新的服務(wù)實(shí)例。
IServiceProvider除了為我們提供所需的服務(wù)實(shí)例之外,對(duì)于由它提供的服務(wù)實(shí)例,它還肩負(fù)起回收釋放之責(zé)。這里所說(shuō)的回收釋放與.NET Core自身的垃圾回收機(jī)制無(wú)關(guān),僅僅針對(duì)于自身類型實(shí)現(xiàn)了IDisposable接口的服務(wù)實(shí)例(下面簡(jiǎn)稱為Disposable服務(wù)實(shí)例),針對(duì)服務(wù)實(shí)例的釋放體現(xiàn)為調(diào)用它們的Dispose方法。IServiceProvider針對(duì)服務(wù)實(shí)例采用的回收釋放策略取決于對(duì)應(yīng)服務(wù)注冊(cè)的生命周期模式,具體服務(wù)回收策略主要體現(xiàn)為如下兩點(diǎn):
Singleton:提供Disposable服務(wù)實(shí)例保存在作為根容器的IServiceProvider對(duì)象上,只有后者被釋放的時(shí)候這些Disposable服務(wù)實(shí)例才能被釋放。
Scoped和Transient:IServiceProvider對(duì)象會(huì)保存由它提供的Disposable服務(wù)實(shí)例,當(dāng)自己被釋放的時(shí)候,這些Disposable會(huì)被釋放。
1、
如下面的代碼片段所示,IServiceProvider接口定義了唯一的方法GetService方法根據(jù)指定的服務(wù)類型來(lái)提供對(duì)應(yīng)的服務(wù)實(shí)例。當(dāng)我們?cè)诶冒?wù)注冊(cè)的IServiceCollection對(duì)象創(chuàng)建對(duì)作為DI容器的IServiceProvider對(duì)象之后,我們只需要將服務(wù)注冊(cè)的服務(wù)類型(對(duì)應(yīng)于ServiceDescriptor的ServiceType屬性)作為參數(shù)調(diào)用GetService方法,后者就能根據(jù)服務(wù)注冊(cè)信息為我們提供對(duì)應(yīng)的服務(wù)實(shí)例。
public interface IServiceProvider
{
object GetService(Type serviceType);
}
public static class ServiceCollectionContainerBuilderExtensions
{
public static ServiceProvider BuildServiceProvider(this IServiceCollection services);
}
2、構(gòu)造函數(shù)的選擇
對(duì)于通過(guò)調(diào)用IServiceCollection的BuildServiceProvider方法創(chuàng)建的IServiceProvider來(lái)說(shuō),當(dāng)我們通過(guò)指定服務(wù)類型調(diào)用其GetService方法以獲取對(duì)應(yīng)的服務(wù)實(shí)例的時(shí)候,它總是會(huì)根據(jù)提供的服務(wù)類型從服務(wù)注冊(cè)列表中找到對(duì)應(yīng)的ServiceDescriptor對(duì)象,并根據(jù)后者提供所需的服務(wù)實(shí)例。
ServiceDescriptor具有三個(gè)不同的構(gòu)造函數(shù),分別對(duì)應(yīng)著服務(wù)實(shí)例最初的三種創(chuàng)建方式,我們可以提供一個(gè)Func<IServiceProvider, object>對(duì)象作為工廠來(lái)創(chuàng)建對(duì)應(yīng)的服務(wù)實(shí)例,也可以直接提供一個(gè)創(chuàng)建好的服務(wù)實(shí)例。如果我們提供的是服務(wù)的實(shí)現(xiàn)類型,那么最終提供的服務(wù)實(shí)例將通過(guò)調(diào)用該類型的某個(gè)構(gòu)造函數(shù)來(lái)創(chuàng)建,那么構(gòu)造函數(shù)時(shí)通過(guò)怎樣的策略被選擇出來(lái)的呢?
如果IServiceProvider對(duì)象試圖通過(guò)調(diào)用構(gòu)造函數(shù)的方式來(lái)創(chuàng)建服務(wù)實(shí)例,傳入構(gòu)造函數(shù)的所有參數(shù)必須先被初始化,最終被選擇出來(lái)的構(gòu)造函數(shù)必須具備一個(gè)基本的條件:IServiceProvider能夠提供構(gòu)造函數(shù)的所有參數(shù)。
我們?cè)谝粋€(gè)控制臺(tái)應(yīng)用中定義了四個(gè)服務(wù)接口(IFoo、IBar、IBaz和IGux)以及實(shí)現(xiàn)它們的四個(gè)服務(wù)類(Foo、Bar、Baz和Gux)。如下面的代碼片段所示,我們?yōu)镚ux定義了三個(gè)構(gòu)造函數(shù),參數(shù)均為我們定義了服務(wù)接口類型。為了確定IServiceProvider最終選擇哪個(gè)構(gòu)造函數(shù)來(lái)創(chuàng)建目標(biāo)服務(wù)實(shí)例,我們?cè)跇?gòu)造函數(shù)執(zhí)行時(shí)在控制臺(tái)上輸出相應(yīng)的指示性文字。
public interface IFoo {}
public interface IBar {}
public interface IBaz {}
public interface IGux {}
public class Foo : IFoo {}
public class Bar : IBar {}
public class Baz : IBaz {}
public class Gux : IGux
{
public Gux(IFoo foo) => Console.WriteLine("Selected constructor: Gux(IFoo)");
public Gux(IFoo foo, IBar bar) => Console.WriteLine("Selected constructor: Gux(IFoo, IBar)");
public Gux(IFoo foo, IBar bar, IBaz baz) => Console.WriteLine("Selected constructor: Gux(IFoo, IBar, IBaz)");
}
在如下這段演示程序中我們創(chuàng)建了一個(gè)ServiceCollection對(duì)象并在其中添加針對(duì)IFoo、IBar以及IGux這三個(gè)服務(wù)接口的服務(wù)注冊(cè),針對(duì)服務(wù)接口IBaz的注冊(cè)并未被添加。我們利用由它創(chuàng)建的IServiceProvider來(lái)提供針對(duì)服務(wù)接口IGux的實(shí)例,究竟能否得到一個(gè)Gux對(duì)象呢?如果可以,它又是通過(guò)執(zhí)行哪個(gè)構(gòu)造函數(shù)創(chuàng)建的呢?
class Program
{
static void Main(string[] args)
{
new ServiceCollection()
.AddTransient<IFoo, Foo>()
.AddTransient<IBar, Bar>()
.AddTransient<IGux, Gux>()
.BuildServiceProvider()
.GetServices<IGux>();
}
}
對(duì)于定義在Gux中的三個(gè)構(gòu)造函數(shù)來(lái)說(shuō),由于創(chuàng)建IServiceProvider提供的IServiceCollection集合包含針對(duì)接口IFoo和IBar的服務(wù)注冊(cè),所以它能夠提供前面兩個(gè)構(gòu)造函數(shù)的所有參數(shù)。由于第三個(gè)構(gòu)造函數(shù)具有一個(gè)類型為IBaz的參數(shù),這無(wú)法通過(guò)IServiceProvider來(lái)提供。根據(jù)我們上面介紹的第一個(gè)原則(IServiceProvider能夠提供構(gòu)造函數(shù)的所有參數(shù)),Gux的前兩個(gè)構(gòu)造函數(shù)會(huì)成為合法的候選構(gòu)造函數(shù),那么IServiceProvider最終會(huì)選擇哪一個(gè)呢?
在所有合法的候選構(gòu)造函數(shù)列表中,最終被選擇出來(lái)的構(gòu)造函數(shù)具有這么一個(gè)特征:每一個(gè)候選構(gòu)造函數(shù)的參數(shù)類型集合都是這個(gè)構(gòu)造函數(shù)參數(shù)類型集合的子集。如果這樣的構(gòu)造函數(shù)并不存在,一個(gè)類型為InvalidOperationException的異常會(huì)被拋出來(lái)。根據(jù)這個(gè)原則,Gux的第二個(gè)構(gòu)造函數(shù)的參數(shù)類型包括IFoo和IBar,而第一個(gè)構(gòu)造函數(shù)僅僅具有一個(gè)類型為IFoo的參數(shù),最終被選擇出來(lái)的會(huì)是Gux的第二個(gè)構(gòu)造函數(shù),所有運(yùn)行我們的實(shí)例程序?qū)?huì)在控制臺(tái)上產(chǎn)生如下圖所示的輸出結(jié)果。

接下來(lái)我們對(duì)實(shí)例程序略加改動(dòng)。如下面的代碼片段所示,我們只為Gux定義兩個(gè)構(gòu)造函數(shù),它們都具有兩個(gè)參數(shù),參數(shù)類型分別為IFoo&IBar和IBar&IBaz。我們將針對(duì)IBaz/Baz的服務(wù)注冊(cè)添加到創(chuàng)建的ServiceCollection對(duì)象上。
class Program
{
static void Main(string[] args)
{
new ServiceCollection()
.AddTransient<IFoo, Foo>()
.AddTransient<IBar, Bar>()
.AddTransient<IBaz, Baz>()
.AddTransient<IGux, Gux>()
.BuildServiceProvider()
.GetServices<IGux>();
}
}
public class Gux : IGux
{
public Gux(IFoo foo, IBar bar) {}
public Gux(IBar bar, IBaz baz) {}
}
對(duì)于Gux的兩個(gè)構(gòu)造函數(shù),雖然它們的參數(shù)均能夠由IServiceProvider來(lái)提供,但是并沒(méi)有一個(gè)構(gòu)造函數(shù)的參數(shù)類型集合能夠成為所有有效構(gòu)造函數(shù)參數(shù)類型集合的超集,所以ServiceProvider無(wú)法選擇出一個(gè)最佳的構(gòu)造函數(shù)。運(yùn)行該程序后會(huì)拋出如下圖所示的InvalidOperationException異常,并提示無(wú)法從兩個(gè)候選的構(gòu)造函數(shù)中選擇出一個(gè)最優(yōu)的來(lái)創(chuàng)建服務(wù)實(shí)例。

三、使用ASP.NET Core自帶DI
1.如何注入自己的服務(wù)
首先,我們編寫(xiě)我們自己的測(cè)試服務(wù)如下:
public class TestService: ITestService
{
public TestService()
{
MyProperty = Guid.NewGuid();
}
public Guid MyProperty { get; set; }
public List<string> GetList(string a)
{
return new List<string>() { "LiLei", "ZhangSan", "LiSi" };
}
}
編寫(xiě)對(duì)應(yīng)的接口代碼如下:
public interface ITestService
{
Guid MyProperty { get; }
List<string> GetList(string a);
}
然后,我們要在Startup類引用 Microsoft.Extensions.DependencyInjection,
修改ConfigureServices方法,如下:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//這里就是注入服務(wù)
services.AddTransient<ITestService, TestService>();
}
這樣,我們就完成了初步的注入操作.
那么我們?nèi)绾问褂梦覀冏⑷氲姆?wù)呢?
我們到控制器,編寫(xiě)代碼如下:
public class DITestController : Controller
{
private readonly ITestService _testService;
public DITestController(ITestService testService)
{
_testService = testService;
}
public IActionResult Index()
{
ViewBag.date = _testService.GetList("");
return View();
}
}
我們編寫(xiě)我們的index視圖如下:
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
@foreach (var item in ViewBag.date)
{
<h2>@item</h2>
}
最終效果如下:

2.注入服務(wù)的生命周期
這里的生命周期針對(duì)每個(gè)HTTP請(qǐng)求的上下文,也就是服務(wù)范圍的生命周期與每個(gè)請(qǐng)求上下文綁定在一起

Transient(瞬時(shí)的):每次請(qǐng)求時(shí)都會(huì)創(chuàng)建的瞬時(shí)生命周期服務(wù)。
Scoped(作用域的):在同作用域,服務(wù)每個(gè)請(qǐng)求只創(chuàng)建一次。
Singleton(唯一的):全局只創(chuàng)建一次,第一次被請(qǐng)求的時(shí)候被創(chuàng)建,然后就一直使用這一個(gè)。
四、使用Autofac
首先,我們需要從nuget引用相關(guān)的包:Autofac.Extensions.DependencyInjection
然后,我們修改Startup中的ConfigureServices代碼如下:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//加入Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
這里我們使用了AutoFac的功能之一,模塊化注入.也就是RegisterModule 這里, DefaultModule是我們的注入模塊,代碼很簡(jiǎn)單,如下:
public class DefaultModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//注入測(cè)試服務(wù)
builder.RegisterType<TestService>().As<ITestService>();
}
}
在上面的代碼中,我們配置IServiceProvider從Autofac容器中解析(設(shè)置一個(gè)有效的Autofac服務(wù)適配器)。
然后在整個(gè)框架中使用它來(lái)解析控制器的依賴關(guān)系,并在HttpContext上公開(kāi)所有其他用例的服務(wù)定位。
這樣我們就完成了初步的Autofac容器替換.下面我們創(chuàng)建控制器來(lái)看看效果.代碼如下:
public class DITestController : Controller
{
private readonly ITestService _testService;
public DITestController(ITestService testService)
{
_testService = testService;
}
public IActionResult Index()
{
ViewBag.date = _testService.GetList("Name");
return View();
}
}
一、類型注冊(cè)
1、Autofac類型注冊(cè)
//1.類型注冊(cè)
containerBuilder.RegisterType<TestService>().As<ITestService>();
2.使用Module注冊(cè)
//2.使用Module注冊(cè)
containerBuilder.RegisterModule<DefaultModule>();
3.程序集批量注冊(cè)
為了統(tǒng)一管理 IoC 相關(guān)的代碼,并避免在底層類庫(kù)中到處引用 Autofac 這個(gè)第三方組件,定義了一個(gè)專門(mén)用于管理需要依賴注入的接口與實(shí)現(xiàn)類的空接口 IDependency:
/// <summary>
/// 依賴注入接口,表示該接口的實(shí)現(xiàn)類將自動(dòng)注冊(cè)到IoC容器中
/// </summary>
public interface IDependency
{
}
這個(gè)接口沒(méi)有任何方法,不會(huì)對(duì)系統(tǒng)的業(yè)務(wù)邏輯造成污染,所有需要進(jìn)行依賴注入的接口,都要繼承這個(gè)空接口,例如:
public interface ITestService : IDependency
{
Guid MyProperty { get; }
List<string> GetList(string a);
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//加入Autofac
var containerBuilder = new ContainerBuilder();
//程序集批量注冊(cè)
Type baseType = typeof(IDependency);
var dataAccess = Assembly.GetExecutingAssembly();
containerBuilder.RegisterAssemblyTypes(dataAccess)
.Where(type => baseType.IsAssignableFrom(type) && !type.IsAbstract)
.AsImplementedInterfaces().InstancePerLifetimeScope();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
1、InstancePerDependency
對(duì)每一個(gè)依賴或每一次調(diào)用創(chuàng)建一個(gè)新的唯一的實(shí)例。這也是默認(rèn)的創(chuàng)建實(shí)例的方式。
2、InstancePerLifetimeScope
在一個(gè)生命周期域中,每一個(gè)依賴或調(diào)用創(chuàng)建一個(gè)單一的共享的實(shí)例,且每一個(gè)不同的生命周期域,實(shí)例是唯一的,不共享的。
3、InstancePerMatchingLifetimeScope
在一個(gè)做標(biāo)識(shí)的生命周期域中,每一個(gè)依賴或調(diào)用創(chuàng)建一個(gè)單一的共享的實(shí)例。打了標(biāo)識(shí)了的生命周期域中的子標(biāo)識(shí)域中可以共享父級(jí)域中的實(shí)例。若在整個(gè)繼承層次中沒(méi)有找到打標(biāo)識(shí)的生命周期域,則會(huì)拋出異常:DependencyResolutionException。
4、InstancePerOwned
在一個(gè)生命周期域中所擁有的實(shí)例創(chuàng)建的生命周期中,每一個(gè)依賴組件或調(diào)用Resolve()方法創(chuàng)建一個(gè)單一的共享的實(shí)例,并且子生命周期域共享父生命周期域中的實(shí)例。若在繼承層級(jí)中沒(méi)有發(fā)現(xiàn)合適的擁有子實(shí)例的生命周期域,則拋出異常:DependencyResolutionException。
5、SingleInstance
每一次依賴組件或調(diào)用Resolve()方法都會(huì)得到一個(gè)相同的共享的實(shí)例。其實(shí)就是單例模式。
6、InstancePerHttpRequest
在一次Http請(qǐng)求上下文中,共享一個(gè)組件實(shí)例。僅適用于asp.net mvc開(kāi)發(fā)
4.Lambda注冊(cè)
//4.Lambda注冊(cè)
containerBuilder.Register(cc =>
{
var TestService = new TestService();
return TestService;
}).As<ITestService>();
5.實(shí)例注冊(cè)
var TestService = new TestService();
containerBuilder.RegisterInstance(TestService).As<ITestService>();
二、類型關(guān)聯(lián)
1、As關(guān)聯(lián)
我們?cè)谶M(jìn)行手動(dòng)關(guān)聯(lián)時(shí),基本都是使用As進(jìn)行關(guān)聯(lián)的
2、批量關(guān)聯(lián)AsImplementedInerfaces
程序集批量注冊(cè)中使用的就是批量關(guān)聯(lián)