摘要:基於.NET Core 7.0WebApi後端架構實戰【2-介入IOC控制反轉】 2023/04/09, ASP.NET Core 7.0, VS2022
引言
Inversion of Control,簡稱IOC,即控制反轉。記得當初剛實習的時候公司的帶我的人和我提到過IOC這個概念,當初完全不知道是
啥東西。後來有幸寫了半年Java,SpringBoot裏面業務開發隨處可見IOC。再後來我寫.Net Core用到的第一個框架Blog.Core項目,它裏
面IRepository與Repository和IServices與Services,這種解耦的程度單說它貫徹依賴倒置原則是非常nice的!.NetCore時代更加註
重面向接口編程,並且它內置輕量型的IOC容器可幫助我們靈活的進行開發。
依賴注入
Dependency Injection,何爲依賴注入?由容器動態的將對象依賴的資源注入到對象之中。假設容器在構造對象A時,對象A的構造依賴對象B、對象C、對象D這些參數,容器會將這些依賴關係自動構造好,最大限度的完成程序的解耦。依賴注入(Dependency Injection,簡稱DI)就是構造A對象時,需要依賴B對象,那麼就先構造B對象作爲參數傳遞到A對象,這種對象初始化並注入的技術就叫做依賴注入。
.NetCore本身就集成了一個輕量型的IOC容器,我們可以把程序中的抽象(Interface)或業務類根據需要的生命週期配置到容器中。他不是一次把所有的實例都構造出來,當你在用的時候他纔會去構造實例!.NetCore內置的IOC容器只支持構造函數注入!
如下三種生命週期的注入:
Singleton
:在第一次被請求的時候被創建,對象的構造函數只會執行一次然後一直會存在內存中之後就一直使用這一個實例。如果在多線程中使用了Singleton,要考慮線程安全的問題,保證它不會有衝突。
Scoped
:一個請求只創建這一個實例,請求會被當成一個範圍,也就是說單個請求對象的構造函數只會執行一次,同一次請求內注入多次也用的是同一個對象。假如一個請求會穿透應用層、領域層、基礎設施層等等各層,在穿層的過程中肯定會有同一個類的多次注入,那這些多次注入在這個作用域下維持的就是單例。例如工作單元模式就可以藉助這種模式的生命週期來實現,像EF Core的DbContext上下文就被註冊爲作用域服務。
Transient
:每次請求時都會創建一個新的實例,只要注入一次,構造函數就會執行一次即實例化一次。Scoped、Transient的區別是你在同一個上下文中是否期望使用同一個實例,如果是,用Scoped,反之則使用Transient。
😊接下來我們來看下.Net Core中相關IOC核心源碼😊
IOC源碼
ServiceDescriptor
這個類是用來描述服務註冊時的服務類型、實例類型、實現類型、生命週期等信息。就如同他本身的描述:Describes a service with its service type, implementation, and lifetime.
DI容器ServiceProvider之所以能夠根據我們給定的服務類型(一般是一個接口類型)提供一個能夠開箱即用的服務實例,是因爲我們預先註冊了相應的服務描述信息,就是說ServiceDescriptor爲容器提供了正確的服務信息以供容器選擇對應的實例。
這個類一共有四個構造函數,本質其實就是給描述具體註冊的Lifetime、ServiceType、ImplementationType、ImplementationInstance、ImplementationFactory賦值。請看源碼:
這裏的話只列舉其中的一個構造函數,因爲我發現在文章中源碼不能羅列的太多,不然讓人看得嫌煩😅
下面這個構造函數需要兩個參數,分別是服務元素的類型,服務元素實例,默認是單例的,其它的幾個構造函數都與其很相仿。
/// <summary>
/// Initializes a new instance of <see cref="ServiceDescriptor"/> with the specified <paramref name="instance"/>
/// as a <see cref="ServiceLifetime.Singleton"/>.
/// </summary>
/// <param name="serviceType">The <see cref="Type"/> of the service.</param>
/// <param name="instance">The instance implementing the service.</param>
public ServiceDescriptor(
Type serviceType,
object instance)
: this(serviceType, ServiceLifetime.Singleton)
{
if (serviceType == null)
{
throw new ArgumentNullException(nameof(serviceType));
}
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
ImplementationInstance = instance;
}
我們再來看下它裏面幾個重要的函數:
①Describe()
函數,可以看到這個函數也是用來生成ServiceDescriptor
類的,它們分別對應上面的構造函數,也就是說爲實例化ServiceDescriptor
提供另外一種方式。源碼如下:
private static ServiceDescriptor Describe<TService, TImplementation>(ServiceLifetime lifetime)
where TService : class
where TImplementation : class, TService
{
return Describe(
typeof(TService),
typeof(TImplementation),
lifetime: lifetime);
}
/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="serviceType"/>, <paramref name="implementationType"/>,
/// and <paramref name="lifetime"/>.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <param name="implementationType">The type of the implementation.</param>
/// <param name="lifetime">The lifetime of the service.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime)
{
return new ServiceDescriptor(serviceType, implementationType, lifetime);
}
/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="serviceType"/>, <paramref name="implementationFactory"/>,
/// and <paramref name="lifetime"/>.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <param name="lifetime">The lifetime of the service.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime)
{
return new ServiceDescriptor(serviceType, implementationFactory, lifetime);
}
②Singleton()
函數,這個函數一共有七個重載,我只貼出其中三個方法,其它幾個都差不多,總之就是一句話用於生成單例化的ServiceDescriptor
類。源碼如下:
/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
/// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <typeparam name="TImplementation">The type of the implementation.</typeparam>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Singleton<TService, TImplementation>()
where TService : class
where TImplementation : class, TService
{
return Describe<TService, TImplementation>(ServiceLifetime.Singleton);
}
/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationType"/>
/// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationType">The type of the implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Singleton(Type service, Type implementationType)
{
if (service == null)
{
throw new ArgumentNullException(nameof(service));
}
if (implementationType == null)
{
throw new ArgumentNullException(nameof(implementationType));
}
return Describe(service, implementationType, ServiceLifetime.Singleton);
}
/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="serviceType"/>, <paramref name="implementationInstance"/>,
/// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <param name="implementationInstance">The instance of the implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Singleton(
Type serviceType,
object implementationInstance)
{
if (serviceType == null)
{
throw new ArgumentNullException(nameof(serviceType));
}
if (implementationInstance == null)
{
throw new ArgumentNullException(nameof(implementationInstance));
}
return new ServiceDescriptor(serviceType, implementationInstance);
}
③Scoped()
函數,這個函數有五個重載,其實他內部還是調用Describe()
方法來生成作用域的ServiceDescriptor
類。源碼如下:
/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
/// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <typeparam name="TImplementation">The type of the implementation.</typeparam>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Scoped<TService, TImplementation>()
where TService : class
where TImplementation : class, TService
{
return Describe<TService, TImplementation>(ServiceLifetime.Scoped);
}
/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationType"/>
/// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationType">The type of the implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Scoped(Type service, Type implementationType)
{
return Describe(service, implementationType, ServiceLifetime.Scoped);
}
通過Scoped()
方法我們可以這樣註冊:
services.Add(ServiceDescriptor.Scoped<IPersonService, PersonService>());
//services.Add(ServiceDescriptor.Scoped(typeof(IPersonService),typeof(PersonService)));
//或
services.Add(ServiceDescriptor.Transient<IPersonService, PersonService>());
//services.Add(ServiceDescriptor.Transient(typeof(IPersonService), typeof(PersonService)));
//或
services.Add(ServiceDescriptor.Singleton<IPersonService, PersonService>());
//services.Add(ServiceDescriptor.Singleton(typeof(IPersonService), typeof(PersonService)));
這種註冊方式是通過ServiceDescriptor
自身的操作去註冊相關實例,我們拿出來其中一個Transient
方法看一下具體實現:
public static ServiceDescriptor Transient<TService, TImplementation>()
where TService : class
where TImplementation : class, TService
{
//都是在調用Describe
return Describe<TService, TImplementation>(ServiceLifetime.Transient);
}
public static ServiceDescriptor Transient(Type service, Type implementationType)
{
//都是在調用Describe
return Describe(service, implementationType, ServiceLifetime.Transient);
}
public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime)
{
//還是返回ServiceDescriptor實例
return new ServiceDescriptor(serviceType, implementationType, lifetime);
}
public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime)
{
//還是返回ServiceDescriptor實例
return new ServiceDescriptor(serviceType, implementationFactory, lifetime);
}
通過這個我們就可以瞭解到ServiceDescriptor.Singleton
、ServiceDescriptor.Scoped
、ServiceDescriptor.Transient
其實是調用的Describe()
方法,Describe()
的本身還是去實例化ServiceDescriptor
,殊途同歸,只是多了種寫法,最終還是去構建ServiceDescriptor
。通過這麼多源碼的分析得出的結論就一點IServiceCollection
註冊的本質就是在構建ServiceDescriptor
集合。
IServiceCollection
IServiceCollection
是一個輕量級的依賴注入容器,來自於Microsoft.Extensions.DependencyInjection
這個包。
我們來看一下它的定義:
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.Extensions.DependencyInjection
{
public interface IServiceCollection :
IList<ServiceDescriptor>,
ICollection<ServiceDescriptor>,
IEnumerable<ServiceDescriptor>,
IEnumerable
{
}
}
可以看到它已經具備集合的增刪改查基本功能了,並且它和ServiceDescriptor
進行了掛鉤,是保存ServiceDescriptor
的數據結構接口。但是裏面並沒有AddScoped
、AddTransient
、AddSingleton
這幾個方法,說明這幾個方法是寫在其它類中的擴展方法。IServiceCollection也沒有定義其任何成員,而是從IList<ServiceDescriptor>、ICollection<ServiceDescriptor>、IEnumerable<ServiceDescriptor>派生
ServiceCollection
我們接下來看下ServiceCollection
的源碼,ServiceCollection
就是ServiceDescriptor
的集合,它全部是對ServiceDescriptor
集合進行操作。如下:
/// <summary>
/// Default implementation of <see cref="IServiceCollection"/>.
/// </summary>
public class ServiceCollection : IServiceCollection
{
private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();
/// <inheritdoc />
public int Count => _descriptors.Count;
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public ServiceDescriptor this[int index]
{
get
{
return _descriptors[index];
}
set
{
_descriptors[index] = value;
}
}
/// <inheritdoc />
public void Clear()
{
_descriptors.Clear();
}
/// <inheritdoc />
public bool Contains(ServiceDescriptor item)
{
return _descriptors.Contains(item);
}
/// <inheritdoc />
public void CopyTo(ServiceDescriptor[] array, int arrayIndex)
{
_descriptors.CopyTo(array, arrayIndex);
}
/// <inheritdoc />
public bool Remove(ServiceDescriptor item)
{
return _descriptors.Remove(item);
}
/// <inheritdoc />
public IEnumerator<ServiceDescriptor> GetEnumerator()
{
return _descriptors.GetEnumerator();
}
void ICollection<ServiceDescriptor>.Add(ServiceDescriptor item)
{
_descriptors.Add(item);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
public int IndexOf(ServiceDescriptor item)
{
return _descriptors.IndexOf(item);
}
/// <inheritdoc />
public void Insert(int index, ServiceDescriptor item)
{
_descriptors.Insert(index, item);
}
/// <inheritdoc />
public void RemoveAt(int index)
{
_descriptors.RemoveAt(index);
}
}
ServiceCollection
是一個ServiceDescriptor
隊列實現類,主要作用是保存ServiceDescriptor
對象,它默認定義的ServiceDescriptor
集合是私有的。ServiceCollection
承載了一個List
的集合,由於實現了IList
接口,所以該類實現了接口的方法,實現了對List
集合的操作。總之ServiceCollection
就是用來操作ServiceDescriptor
對象的。
ServiceCollectionServiceExtensions
這個類裏面都是我們平時服務註冊用到的常用方法,即:AddTransient
、AddScoped
、AddSingleton
。它們其實方法實現都差不多,由於篇幅原因我只貼出部分源碼:
/// <summary>
/// Adds a scoped service of the type specified in <paramref name="serviceType"/> with an
/// implementation of the type specified in <paramref name="implementationType"/> to the
/// specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
/// <param name="serviceType">The type of the service to register.</param>
/// <param name="implementationType">The implementation type of the service.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
/// <seealso cref="ServiceLifetime.Scoped"/>
public static IServiceCollection AddScoped(
this IServiceCollection services,
Type serviceType,
Type implementationType)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (serviceType == null)
{
throw new ArgumentNullException(nameof(serviceType));
}
if (implementationType == null)
{
throw new ArgumentNullException(nameof(implementationType));
}
return Add(services, serviceType, implementationType, ServiceLifetime.Scoped);
}
private static IServiceCollection Add(
IServiceCollection collection,
Type serviceType,
Type implementationType,
ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
collection.Add(descriptor);
return collection;
}
private static IServiceCollection Add(
IServiceCollection collection,
Type serviceType,
Func<IServiceProvider, object> implementationFactory,
ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationFactory, lifetime);
collection.Add(descriptor);
return collection;
}
可以看到AddScoped()
方法最終調用的還是Add()
方法,一共有兩個重載,每個注入的實例的生命週期是通過ServiceLifetime
這個枚舉來判斷的。你看它代碼都非常簡單,就是構建ServiceDescriptor
實例然後添加到IServiceCollection
即IList
中。下圖這種只註冊具體類型或者具體實例的方法,其實註冊單類型的方法,也是通過調用的注入實例到抽象的方法,只不過是將自己註冊給了自己。
來看一下ServiceLifetime
的源碼,這個枚舉是爲了我們註冊服務實例的聲明週期的,非常清晰不在過多講述:
/// <summary>
/// Specifies the lifetime of a service in an <see cref="IServiceCollection"/>.
/// </summary>
public enum ServiceLifetime
{
/// <summary>
/// Specifies that a single instance of the service will be created.
/// </summary>
Singleton,
/// <summary>
/// Specifies that a new instance of the service will be created for each scope.
/// </summary>
/// <remarks>
/// In ASP.NET Core applications a scope is created around each server request.
/// </remarks>
Scoped,
/// <summary>
/// Specifies that a new instance of the service will be created every time it is requested.
/// </summary>
Transient
}
IServiceProvider
IServiceProvider
是依賴注入的核心接口 ,但是它裏面的結構比較簡單,只有一個方法,用於從容器中獲取實例,其他獲取實例的方法都是根據這個方法擴展而來:
/// <summary>Defines a mechanism for retrieving a service object; that is, an object that provides custom support to other objects.</summary>
public interface IServiceProvider
{
/// <summary>Gets the service object of the specified type.</summary>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <returns>A service object of type <paramref name="serviceType" />.
///
/// -or-
///
/// <see langword="null" /> if there is no service object of type <paramref name="serviceType" />.</returns>
object? GetService(Type serviceType);
}
IServiceProvider
作爲服務提供者,是根據IServiceCollection
構建出來的:
IServiceProvider serviceProvider = services.BuildServiceProvider();
BuildServiceProvider
來自於ServiceCollectionContainerBuilderExtensions
擴展類下的擴展方法,源碼如下:
/// <summary>
/// Extension methods for building a <see cref="ServiceProvider"/> from an <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionContainerBuilderExtensions
{
/// <summary>
/// Creates a <see cref="ServiceProvider"/> containing services from the provided <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param>
/// <returns>The <see cref="ServiceProvider"/>.</returns>
public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
{
return BuildServiceProvider(services, ServiceProviderOptions.Default);
}
/// <summary>
/// Creates a <see cref="ServiceProvider"/> containing services from the provided <see cref="IServiceCollection"/>
/// optionally enabling scope validation.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param>
/// <param name="validateScopes">
/// <c>true</c> to perform check verifying that scoped services never gets resolved from root provider; otherwise <c>false</c>.
/// </param>
/// <returns>The <see cref="ServiceProvider"/>.</returns>
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
{
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes });
}
/// <summary>
/// Creates a <see cref="ServiceProvider"/> containing services from the provided <see cref="IServiceCollection"/>
/// optionally enabling scope validation.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param>
/// <param name="options">
/// Configures various service provider behaviors.
/// </param>
/// <returns>The <see cref="ServiceProvider"/>.</returns>
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return new ServiceProvider(services, options);
}
}
當我們構建ServiceProvider
實例的時候,如果沒有傳遞參數,他會創建一個ServiceProviderOptions
的默認實例,ServiceProviderOptions
這個類是 DI 容器的配置選項,主要可以用來配置ServiceProvider
的行爲,體現在其內部幾個屬性的值上面。
public class ServiceProviderOptions
{
internal static readonly ServiceProviderOptions Default = new ServiceProviderOptions();
/// <summary>
/// 是否在每個作用域中驗證服務的依賴關係。默認值爲 false。
/// </summary>
public bool ValidateScopes { get; set; }
/// <summary>
/// 是否在構建 ServiceProvider 時驗證所有服務的依賴關係。默認值爲 true。
/// </summary>
public bool ValidateOnBuild { get; set; }
/// <summary>
/// 控制 ServiceProvider 的行爲
/// </summary>
internal ServiceProviderMode Mode { get; set; }
}
internal enum ServiceProviderMode
{
Default,//這是默認的服務提供程序模式,它使用基於反射的動態代碼生成來創建服務對象。在大多數情況下,這種模式可以滿足我們的需求。
Dynamic,//這種模式也使用基於反射的動態代碼生成來創建服務對象,但是它會嘗試在編譯時生成代碼,以提高性能。這種模式需要在應用程序啓動時進行編譯,因此可能會增加啓動時間。
Runtime,//這種模式使用基於反射的動態代碼生成來創建服務對象,但是它會嘗試在運行時生成代碼。這種模式可以提高性能,但是會增加內存使用量和啓動時間。
Expressions,//這種模式使用表達式樹來創建服務對象。它比基於反射的動態代碼生成更快,但是可能會增加內存使用量。
ILEmit,//這種模式使用基於 IL 的代碼生成來創建服務對象。它比基於反射的動態代碼生成更快,但是需要更多的內存和啓動時間。
}
ValidateScopes
屬性用於控制服務提供程序是否檢查服務作用域的範圍:
-
如果開啓了範圍檢查,程序將會在創建服務對象時檢查服務對象的作用域是否與其依賴項的作用域匹配。如果不匹配,服務提供程序將會拋出一個異常,以避免出現潛在的 bug。開啓作用域範圍檢查可以幫助我們在開發過程中及時發現問題,確保應用程序的穩定性和可靠性。特別是在使用多個作用域的情況下,開啓作用域範圍檢查可以避免出現一些意想不到的問題。
-
如果不開啓範圍檢查,程序將不會進行作用域範圍檢查。這樣可以提高服務對象的創建速度,但是也可能會導致一些潛在的 bug,因爲服務對象的作用域與其依賴項的作用域不匹配。
ValidateOnBuild
屬性用於指定 DI 容器在創建服務實例時是否進行作用域範圍檢查,主要會檢查服務實例的依賴關係是否在同一個作用域範圍內,以確保服務實例的依賴關係正確:
-
如果開啓了依賴關係檢查,容器會在創建服務實例時進行作用域範圍檢查,如果檢查失敗,則會拋出異常。
-
如果不開啓依賴關係檢查,容器不會進行作用域範圍檢查,這可能會導致服務實例的依賴關係錯誤,從而導致應用程序出現問題。
因此,建議在開發過程中開啓ValidateScopes 與ValidateOnBuild 屬性,以確保應用程序的穩定性和可靠性。而在生產環境中,可以考慮關閉這兩個屬性以提高性能。
ServiceProvider
ServiceProvider
是.NET Core 中依賴注入容器的核心實現,它提供了註冊服務、獲取服務、創建作用域等基本功能,並支持配置選項和模式,它也是IServiceProvider
的默認實現類,這裏的話我只貼出提供服務相關的代碼:
/// <summary>
/// The default IServiceProvider.
/// </summary>
public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback, IAsyncDisposable
{
private readonly IServiceProviderEngine _engine;
private readonly CallSiteValidator _callSiteValidator;
internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
IServiceProviderEngineCallback callback = null;
if (options.ValidateScopes)
{
callback = this;
_callSiteValidator = new CallSiteValidator();
}
//通過ServiceProviderOptions中的枚舉值判斷用哪種方式實例化對象
switch (options.Mode)
{
case ServiceProviderMode.Default:
!NETCOREAPP
_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
e
if (RuntimeFeature.IsSupported("IsDynamicCodeCompiled"))
{
_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
}
else
{
// Don't try to compile Expressions/IL if they are going to get interpreted
_engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
}
if
break;
case ServiceProviderMode.Dynamic:
_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
break;
case ServiceProviderMode.Runtime:
_engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
break;
IL_EMIT
case ServiceProviderMode.ILEmit:
_engine = new ILEmitServiceProviderEngine(serviceDescriptors, callback);
break;
if
case ServiceProviderMode.Expressions:
_engine = new ExpressionsServiceProviderEngine(serviceDescriptors, callback);
break;
default:
throw new NotSupportedException(nameof(options.Mode));
}
//當前創建服務實例時是否進行作用域範圍檢查
if (options.ValidateOnBuild)
{
List<Exception> exceptions = null;
foreach (var serviceDescriptor in serviceDescriptors)
{
try
{
_engine.ValidateService(serviceDescriptor);
}
catch (Exception e)
{
exceptions = exceptions ?? new List<Exception>();
exceptions.Add(e);
}
}
if (exceptions != null)
{
throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray());
}
}
}
/// <summary>
/// 通過IServiceProviderEngine獲取實例
/// </summary>
public object GetService(Type serviceType) => _engine.GetService(serviceType);
}
IServiceProviderEngine
接口是用於創建和管理依賴項注入容器的引擎,這個接口是爲了提高依賴項注入容器的性能而設計的。
IServiceProviderEngine
接口定義了一個CreateServiceProvider
方法,該方法使用 IServiceProviderEngine
實現創建一個新的 IServiceProvider
實例。
具體來說,IServiceProviderEngine
接口的作用是:
- 提供了一個可擴展的接口,允許開發人員實現自定義的依賴注入容器。
-
通過使用編譯時生成的代碼,提高了依賴注入容器的性能。
-
提供了一種更靈活的方式來管理依賴注入容器,以支持更高級的場景,例如模塊化應用程序和插件式架構。
我們上面說到從容器中獲取實例使用IServiceProvider
中的GetService()
方法,但是遠不止這一種,還有我們的子作用域的創建是在哪個類中實現的,接下來我們來看一下ServiceProvider
的另一個擴展類ServiceProviderServiceExtensions
,源碼如下:
/// <summary>
/// Extension methods for getting services from an <see cref="IServiceProvider" />.
/// </summary>
public static class ServiceProviderServiceExtensions
{
/// <summary>
/// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <typeparam name="T">The type of service object to get.</typeparam>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
/// <returns>A service object of type <typeparamref name="T"/> or null if there is no such service.</returns>
public static T GetService<T>(this IServiceProvider provider)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
return (T)provider.GetService(typeof(T));
}
/// <summary>
/// Get service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <returns>A service object of type <paramref name="serviceType"/>.</returns>
/// <exception cref="System.InvalidOperationException">There is no service of type <paramref name="serviceType"/>.</exception>
public static object GetRequiredService(this IServiceProvider provider, Type serviceType)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
if (serviceType == null)
{
throw new ArgumentNullException(nameof(serviceType));
}
var requiredServiceSupportingProvider = provider as ISupportRequiredService;
if (requiredServiceSupportingProvider != null)
{
return requiredServiceSupportingProvider.GetRequiredService(serviceType);
}
var service = provider.GetService(serviceType);
if (service == null)
{
throw new InvalidOperationException(Resources.FormatNoServiceRegistered(serviceType));
}
return service;
}
/// <summary>
/// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <typeparam name="T">The type of service object to get.</typeparam>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
/// <returns>A service object of type <typeparamref name="T"/>.</returns>
/// <exception cref="System.InvalidOperationException">There is no service of type <typeparamref name="T"/>.</exception>
public static T GetRequiredService<T>(this IServiceProvider provider)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
return (T)provider.GetRequiredService(typeof(T));
}
/// <summary>
/// Get an enumeration of services of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <typeparam name="T">The type of service object to get.</typeparam>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the services from.</param>
/// <returns>An enumeration of services of type <typeparamref name="T"/>.</returns>
public static IEnumerable<T> GetServices<T>(this IServiceProvider provider)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
return provider.GetRequiredService<IEnumerable<T>>();
}
/// <summary>
/// Get an enumeration of services of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the services from.</param>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <returns>An enumeration of services of type <paramref name="serviceType"/>.</returns>
public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
if (serviceType == null)
{
throw new ArgumentNullException(nameof(serviceType));
}
var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
return (IEnumerable<object>)provider.GetRequiredService(genericEnumerable);
}
/// <summary>
/// Creates a new <see cref="IServiceScope"/> that can be used to resolve scoped services.
/// </summary>
/// <param name="provider">The <see cref="IServiceProvider"/> to create the scope from.</param>
/// <returns>A <see cref="IServiceScope"/> that can be used to resolve scoped services.</returns>
public static IServiceScope CreateScope(this IServiceProvider provider)
{
return provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}
}
創建子作用域:
using (IServiceScope scope = serviceProvider.CreateScope())
{
IServiceProvider scopeProvider = scope.ServiceProvider;
}
GetService(Type serviceType)
:從 DI 容器中獲取指定類型的服務實例。如果 DI 容器中不存在該類型的服務,則返回 null。GetRequiredService(Type serviceType)
:從 DI 容器中獲取指定類型的服務實例。如果 DI 容器中不存在該類型的服務,則拋出異常。GetServices(Type serviceType)
:從 DI 容器中獲取指定類型的所有服務實例。如果 DI 容器中不存在該類型的服務,則返回一個空的IEnumerable
。CreateScope()
:用於創建一個新的作用域(Scope)。作用域是依賴注入容器中的一種機制,它可以用於隔離不同部分之間的依賴關係,同時也可以控制依賴項的生命週期。在一個作用域中,可以創建一個新的IServiceProvider
實例,該實例只能訪問當前作用域及其父作用域中註冊的服務。這樣,就可以在不同的作用域中使用不同的服務實例,從而實現依賴關係的隔離。該方法返回一個IServiceScope
實例,該實例包含了一個新的IServiceProvider
實例和一個作用域對象。可以通過調用IServiceScope
實例的ServiceProvider
屬性來獲取新的IServiceProvider
實例,然後使用該實例來解析當前作用域及其父作用域中註冊的服務。
IServiceProviderFactory
在.Net Core中提供了IServiceProviderFactory
接口來替換默認的容器,通過實現 IServiceProviderFactory
接口,可以創建自己的容器並將其與 ASP.NET Core 的依賴注入系統集成實現自定義的依賴注入容器。這個接口定義了一個方法CreateServiceProvider
,它接受一個泛型參數TContainer
,並返回一個實現了IServiceProvider
接口的對象。源碼如下:
/// <summary>
/// Provides an extension point for creating a container specific builder and an <see cref="IServiceProvider"/>.
/// </summary>
public interface IServiceProviderFactory<TContainerBuilder>
{
/// <summary>
/// Creates a container builder from an <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The collection of services</param>
/// <returns>A container builder that can be used to create an <see cref="IServiceProvider"/>.</returns>
TContainerBuilder CreateBuilder(IServiceCollection services);
/// <summary>
/// Creates an <see cref="IServiceProvider"/> from the container builder.
/// </summary>
/// <param name="containerBuilder">The container builder</param>
/// <returns>An <see cref="IServiceProvider"/></returns>
IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder);
}
IServiceProviderFactory
接口的默認實現是DefaultServiceProviderFactory
類,源碼如下:
/// <summary>
/// Default implementation of <see cref="IServiceProviderFactory{TContainerBuilder}"/>.
/// </summary>
public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
private readonly ServiceProviderOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultServiceProviderFactory"/> class
/// with default options.
/// </summary>
/// <seealso cref="ServiceProviderOptions.Default"/>
public DefaultServiceProviderFactory() : this(ServiceProviderOptions.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultServiceProviderFactory"/> class
/// with the specified <paramref name="options"/>.
/// </summary>
/// <param name="options">The options to use for this instance.</param>
public DefaultServiceProviderFactory(ServiceProviderOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_options = options;
}
/// <inheritdoc />
public IServiceCollection CreateBuilder(IServiceCollection services)
{
return services;
}
/// <inheritdoc />
public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
{
return containerBuilder.BuildServiceProvider(_options);
}
}
可以看到根本麼有什麼邏輯,就是把IServiceCollection
和IServiceProvider
向外提供,方便開發者能夠替換默認的容器實現自定義的容器。
我們來看一下Autofac中的源碼,AutofacServiceProviderFactory
這個類就是Autofac 對IServiceProviderFactory
的一個實現。源碼如下:
/// <summary>
/// A factory for creating a <see cref="ContainerBuilder"/> and an <see cref="IServiceProvider" />.
/// </summary>
public class AutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
{
private readonly Action<ContainerBuilder> _configurationAction;
private readonly ContainerBuildOptions _containerBuildOptions = ContainerBuildOptions.None;
/// <summary>
/// Initializes a new instance of the <see cref="AutofacServiceProviderFactory"/> class.
/// </summary>
/// <param name="containerBuildOptions">The container options to use when building the container.</param>
/// <param name="configurationAction">Action on a <see cref="ContainerBuilder"/> that adds component registrations to the container.</param>
public AutofacServiceProviderFactory(
ContainerBuildOptions containerBuildOptions,
Action<ContainerBuilder> configurationAction = null)
: this(configurationAction) =>
_containerBuildOptions = containerBuildOptions;
/// <summary>
/// Initializes a new instance of the <see cref="AutofacServiceProviderFactory"/> class.
/// </summary>
/// <param name="configurationAction">Action on a <see cref="ContainerBuilder"/> that adds component registrations to the container..</param>
public AutofacServiceProviderFactory(Action<ContainerBuilder> configurationAction = null) =>
_configurationAction = configurationAction ?? (builder => { });
/// <summary>
/// Creates a container builder from an <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The collection of services.</param>
/// <returns>A container builder that can be used to create an <see cref="IServiceProvider" />.</returns>
public ContainerBuilder CreateBuilder(IServiceCollection services)
{
var builder = new ContainerBuilder();
builder.Populate(services);
_configurationAction(builder);
return builder;
}
/// <summary>
/// Creates an <see cref="IServiceProvider" /> from the container builder.
/// </summary>
/// <param name="containerBuilder">The container builder.</param>
/// <returns>An <see cref="IServiceProvider" />.</returns>
public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
{
if (containerBuilder == null)
{
throw new ArgumentNullException(nameof(containerBuilder));
}
var container = containerBuilder.Build(_containerBuildOptions);
return new AutofacServiceProvider(container);
}
}
CreateBuilder
方法接收一個IServiceCollection
類型的參數services
,用於將服務註冊到容器中。在方法中,首先創建一個ContainerBuilder
實例,然後調用其 Populate
方法將services
中的服務註冊到容器中。最後,如果存在自定義配置,就調用configurationAction
對容器進行配置。
CreateServiceProvider
方法接收一個ContainerBuilder
類型的參數containerBuilder
,用於創建IServiceProvider
實例。在方法中,首先調用containerBuilder
的Build
方法創建容器實例,然後將容器實例傳入AutofacServiceProvider
的構造函數中創建IServiceProvider
實例。
UseServiceProviderFactory
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory) where TContainerBuilder : notnull
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
_operations.Add(b => b.UseServiceProviderFactory(factory));
return this;
}
UseServiceProviderFactory
是一個擴展方法,用來註冊自定義容器工廠,可以用builder.Host.UseServiceProviderFactory(new XXXFactory())
來進行配置,從而實現更加靈活的依賴注入。
具體來說,我們可以通過實現IServiceProviderFactory
接口來創建自定義的服務提供程序工廠,其中TContainer
是服務提供程序容器的類型。然後,我們可以在UseServiceProviderFactory
方法中使用這個工廠來創建服務提供程序。
如果我們想要使用 Autofac
作爲服務提供程序容器,就可以實現IServiceProviderFactory
接口來創建Autofac
容器,然後在WebHostBuilder
或HostBuilder
中使用UseServiceProviderFactory
方法來指定使用這個工廠。這樣,我們就可以在應用程序中使用 Autofac
來管理依賴關係。
擴展
不能在項目中使用AddScoped
、AddSingleton
、AddTransient
來註冊對象至容器中,不是不推薦,是不能🤨。後面項目一旦膨脹起來維護變得很麻煩,所以我們要將項目中的dll進行判斷然後將其中的類根據我們自定義的規則判斷自動進行註冊。這裏的話我們還是用.Net Core自帶的IOC進行擴展批量注入。
批量注入-使用接口的方式
我們定義三種類型的接口,讓其分別代表單例、作用域與瞬時,然後掃描項目中的dll中的類型,判斷是否直接或間接繼承這三種接口,如果是則找到實例然後進行註冊🍉
先定義三種類型的接口:
/// <summary>
/// 實現該接口的對象爲作用域生命週期
/// </summary>
public interface IScopeDependency
{
}
/// <summary>
/// 實現該接口的對象爲單例生命週期
/// </summary>
public interface ISingletonDependency
{
}
/// <summary>
/// 實現該接口的對象爲瞬時生命週期
/// </summary>
public interface ITransientDependency
{
}
主要的實現方法,就是篩選我們需要的Type進行註冊:
public static List<Type> BaseTypes { get; set; } = new();
public static IServiceCollection AddDataService(this IServiceCollection services, IConfiguration configuration)
{
List<string> BaseTypeStrArr = (List<string>)configuration.GetSection("BaseTypes").Get(typeof(List<string>));
BaseTypeStrArr.ForEach(item => { BaseTypes.Add(Type.GetType(item)); });
var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
var getFiles = Directory.GetFiles(path, "*.dll").Where(x => Match(x, configuration));
var referencedAssemblies = getFiles.Select(Assembly.LoadFrom).ToList();
var types = referencedAssemblies
.SelectMany(m => m.DefinedTypes)
.Select(type => type.AsType())
.Where(IsMatchType).ToList();
var implementTypes = types.Where(x => x.IsClass).ToList();
var interfaceTypes = types.Where(x => x.IsInterface).ToList();
//循環實例
foreach (var implementType in implementTypes)
{
var type = GetMatchType(implementType);
if (type != null)
{
var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType));
if (interfaceType != null)
{
switch (type.Name)
{
case "IScopeDependency":
services.AddScoped(interfaceType, implementType);
break;
case "ISingletonDependency":
services.AddSingleton(interfaceType, implementType);
break;
case "ITransientDependency":
services.AddTransient(interfaceType, implementType);
break;
default:
break;
}
}
}
}
return services;
}
/// <summary>
/// 判斷類型是否匹配
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsMatchType(Type type)
{
return BaseTypes.Where(m => m != type && m.IsAssignableFrom(type)).Any();
}
/// <summary>
/// 獲取BaseType
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static Type GetMatchType(Type type)
{
return BaseTypes.Where(m => m.IsAssignableFrom(type)).FirstOrDefault();
}
/// <summary>
/// 程序集是否匹配
/// </summary>
public static bool Match(string assemblyName, IConfiguration configuration)
{
assemblyName = Path.GetFileName(assemblyName);
//if (assemblyName.StartsWith($"{AppDomain.CurrentDomain.FriendlyName}.XXX"))
// return false;
return Regex.IsMatch(assemblyName, configuration["MatchAssemblies"], RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
批量注入-使用註解的方式
我們先定義一個特性,可傳入一個生命週期的枚舉參數,然後在需要的類上面標註,如果這個類有接口則將自己和對應的接口進行註冊,如果沒有接口則只注入自己🍎
定義特性類,用於聲明當前標註的類的生命週期
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CoreInjectAttribute : Attribute
{
public CoreInjectAttribute(ServiceLifetime injectType)
{
InjectType = injectType;
}
/// <summary>
/// 生命週期
/// </summary>
public ServiceLifetime InjectType { get; set; }
}
主要的實現方法,代碼都比較簡單,我就不再說了
public static IServiceCollection AddDataService(this IServiceCollection service)
{
var basePath = ApplicationEnvironment.ApplicationBasePath;
var path = AppDomain.CurrentDomain.BaseDirectory;
var assemblies = Directory.GetFiles(path, "ICore.*.dll").Select(Assembly.LoadFrom).ToList();
foreach (var assembly in assemblies)
{
var types = assembly.GetTypes().Where(a => a.GetCustomAttribute<CoreInjectAttribute>() != null)
.ToList();
if (types.Count <= 0) continue;
foreach (var type in types)
{
var injectData = (CoreInjectAttribute)type.GetCustomAttribute<CoreInjectAttribute>();
Type[] interfaces = type.GetInterfaces();
if (!interfaces.Any())
{
service.RegisterType(type, type, injectData.InjectType);
continue;
}
foreach (Type interfaceType in interfaces)
{
service.RegisterType(interfaceType, type, injectData.InjectType);
}
}
}
return service;
}
public static void RegisterType(this IServiceCollection service, Type interfaceType, Type serviceType, ServiceLifetime serviceLifetime)
{
switch (serviceLifetime)
{
case ServiceLifetime.Scoped:
service.AddScoped(interfaceType, serviceType);
break;
case ServiceLifetime.Singleton:
service.AddSingleton(interfaceType, serviceType);
break;
case ServiceLifetime.Transient:
service.AddTransient(interfaceType, serviceType);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
實現屬性注入
因爲.Net Core默認是不支持屬性注入的,我們可以通過IControllerActivator
接口來實現讓控制器支持屬性注入。IControllerActivator
接口的作用是創建控制器實例,當一個請求到達時,框架需要創建一個控制器實例以處理該請求。我們可以通過實現 IControllerActivator
接口來自定義控制器實例的創建過程。其實很簡單,我們定義一個特性,在控制器需要實例的屬性上面標記一下,當請求到來時,我們從容器中取出對應的實例對其進行賦值即可。
[AttributeUsage(AttributeTargets.Property)]
public class InjectAttribute : Attribute
{
}
public class CustomControllerActivator : IControllerActivator
{
/// <summary>
/// 用於創建控制器實例信息,我們可以通過此方法實例屬性信息
/// <summary>
public object Create(ControllerContext context)
{
var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();//得到控制器的類型
var controllerInstance = context.HttpContext.RequestServices.GetService(controllerType);//用容器完成控制器的實例化
//循環標記Inject特性的屬性
foreach (var prop in controllerType.GetProperties().Where(p => p.IsDefined(typeof(InjectAttribute), true)))
{
//從容器中取出對應的實例
var propValue = context.HttpContext.RequestServices.GetService(prop.PropertyType);
prop.SetValue(controllerInstance, propValue);
}
return controllerInstance;
}
/// <summary>
/// 用於釋放控制器實例以便進行垃圾回收,
/// 當 ASP.NET Core 框架不再需要使用控制器實例時,它會調用Release方法來通知IControllerActivator實現釋放控制器實例。
/// 在釋放控制器實例之前,Release 方法可以執行任何必要的清理操作,例如關閉數據庫連接、釋放未託管資源等。
/// <summary>
public void Release(ControllerContext context, object controller)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (controller == null)
{
throw new ArgumentNullException(nameof(controller));
}
var disposable = controller as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
我們先是說了一下IOC的理念,再介紹了.Net Core中三種注入的生命週期,然後看源碼瞭解其內部是如何運作的以及怎樣替換默認容器,最後用.Net Core自帶容器實現批量注入以及擴展屬性注入,希望對大家有所幫助🏝️