[Abp vNext 源碼分析] - 3. 依賴注入與攔截器

1、簡要說明

ABP vNext 框架在使用依賴注入服務的時候,是直接使用的微軟提供的 Microsoft.Extensions.DependencyInjection 包。這裏與原來的 ABP 框架就不同了,原來的 ABP 框架還須要抽象出來一個 IIocManager 用來管理整個 IoC 容器,如今則直接操做 IServiceCollectionIServiceProvider 進行組件的註冊/解析。html

這裏須要注意的是,雖然如今的依賴注入服務是使用微軟官方那一套庫進行操做,可是 ABP vNext 仍是爲咱們提供了組件自動註冊、攔截器這些基礎功能。數組

2、源碼分析

2.1 組件自動註冊

ABP vNext 仍然在其 Core 庫爲咱們提供了三種接口,即 ISingletonDependencyITransientDependencyIScopedDependency 接口,方便咱們的類型/組件自動註冊,這三種接口分別對應了對象的 單例瞬時範圍 生命週期。只要任何類型/接口實現了以上任意接口,ABP vNext 就會在系統啓動時候,將這些對象註冊到 IoC 容器當中。多線程

那麼到底是在何時呢?回顧上一章的模塊系統的文章,在模塊系統調用模塊的 ConfigureService() 的時候,就會有一個 services.AddAssembly(module.Type.Assembly) ,他會將模塊的所屬的程序集傳入。app

public class ModuleLoader : IModuleLoader
{
    // ... 其餘代碼
    protected virtual void ConfigureServices(List<IAbpModuleDescriptor> modules, IServiceCollection services)
    {
        // ... 其餘代碼
        //ConfigureServices
        foreach (var module in modules)
        {
            if (module.Instance is AbpModule abpModule)
            {
                // 是否跳過服務的自動註冊,默認爲 false。
                if (!abpModule.SkipAutoServiceRegistration)
                {
                    services.AddAssembly(module.Type.Assembly);
                }
            }

            module.Instance.ConfigureServices(context);
        }
        // ... 其餘代碼
    }
    // ... 其餘代碼
}

看來核心就在於這個 AddAssembly() 擴展方法了,跳轉到方法的內部,發現真正幹事的是 IConventionalRegistrar 對象,暫且稱之爲規約註冊器,並且咱們能夠擁有多個規約註冊器,你能夠本身實現自動註冊規則。框架

public static IServiceCollection AddAssembly(this IServiceCollection services, Assembly assembly)
{
    // 得到全部規約註冊器,而後調用規約註冊器的 AddAssmbly 方法註冊類型。
    foreach (var registrar in services.GetConventionalRegistrars())
    {
        registrar.AddAssembly(services, assembly);
    }

    return services;
}

該接口定義了三個方法,支持傳入程序集、類型數組、具體類型,他們的默認實現都在抽象類 ConventionalRegistrarBase 當中。異步

public interface IConventionalRegistrar
{
    void AddAssembly(IServiceCollection services, Assembly assembly);

    void AddTypes(IServiceCollection services, params Type[] types);

    void AddType(IServiceCollection services, Type type);
}

抽象類當中的實現也很是簡單,他們最終都是調用的 AddType() 方法來將類型註冊到 IServiceCollection 當中的。async

public abstract class ConventionalRegistrarBase : IConventionalRegistrar
{
    public virtual void AddAssembly(IServiceCollection services, Assembly assembly)
    {
        // 得到程序集內的全部類型,過濾掉抽象類和泛型類型。
        var types = AssemblyHelper
            .GetAllTypes(assembly)
            .Where(
                type => type != null &&
                        type.IsClass &&
                        !type.IsAbstract &&
                        !type.IsGenericType
            ).ToArray();

        AddTypes(services, types);
    }

    public virtual void AddTypes(IServiceCollection services, params Type[] types)
    {
        foreach (var type in types)
        {
            AddType(services, type);
        }
    }

    public abstract void AddType(IServiceCollection services, Type type);
}

因此咱們的重點就在於 AddType() 方法,ABP vNext 框架默認的規約註冊器叫作 DefaultConventionalRegistrar,跳轉到其定義能夠發如今其內部,除了對三種生命週期接口處理以外,若是類型使用了 DependencyAttribute 特性,也會根據該特性的參數配置進行不一樣的註冊邏輯。ide

public override void AddType(IServiceCollection services, Type type)
{
    // 判斷類型是否標註了 DisableConventionalRegistration 特性,若是有標註,則跳過。
    if (IsConventionalRegistrationDisabled(type))
    {
        return;
    }

    // 得到 Dependency 特性,若是沒有則返回 null。
    var dependencyAttribute = GetDependencyAttributeOrNull(type);
    // 優先使用 Dependency 特性所指定的生命週期,若是不存在則根據 type 實現的接口肯定生命週期。
    var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute);

    if (lifeTime == null)
    {
        return;
    }

    // 得到等待註冊的類型定義,類型的定義優先使用 ExposeServices 特性指定的類型,若是沒有則使用
    // 類型當中接口以 I 開始,後面爲實現類型名稱的接口。
    foreach (var serviceType in AutoRegistrationHelper.GetExposedServices(services, type))
    {
        var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);

        if (dependencyAttribute?.ReplaceServices == true)
        {
            // 替換服務。
            services.Replace(serviceDescriptor);
        }
        else if (dependencyAttribute?.TryRegister == true)
        {
            // 註冊服務。
            services.TryAdd(serviceDescriptor);
        }
        else
        {
            // 註冊服務。
            services.Add(serviceDescriptor);
        }
    }
}

這裏就是在 GetLifeTimeOrNull() 內部的 GetServiceLifetimeFromClassHierarcy() 方法肯定了每一個接口對應的生命週期。工具

protected virtual ServiceLifetime? GetServiceLifetimeFromClassHierarcy(Type type)
{
    if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))
    {
        return ServiceLifetime.Transient;
    }

    if (typeof(ISingletonDependency).GetTypeInfo().IsAssignableFrom(type))
    {
        return ServiceLifetime.Singleton;
    }

    if (typeof(IScopedDependency).GetTypeInfo().IsAssignableFrom(type))
    {
        return ServiceLifetime.Scoped;
    }

    return null;
}

若是讀者有用過 AutoFac 或者 Castle Windsor 這些依賴注入框架的話,就知道咱們要註冊一個類型,須要知道該類型的定義和實現。這裏的 AutoRegistrationHelper 工具類就會爲咱們肯定註冊類型的類型定義,與其默認實現。源碼分析

例如我有兩個接口 IDemoTestIDemoTestTwo,和他們的默認實現 DemoTest ,我能夠有如下幾種方法來肯定個人註冊類型。

// 指定了兩個接口類型的實現都是 DemoTest,在註冊的時候就會執行兩次註冊。
// 分別是 services.AddTransient<IDemoTest,DemoTest>();
// services.AddTransient<IDemoTestTwo,DemoTest>();
[ExposeServices(typeof(IDemoTest),typeof(IDemoTestTwo))]
public class DemoTest : IDemoTest,ITransientDependency
{
    
}

// 或者不顯式指定,只須要接口定義符合約定便可。
// services.AddTransient<IDemoTest,DemoTest>();
public class DemoTest : IDemoTest,ITransientDependency
{

}

// 若是連註冊接口都沒有指定,那麼就直接注入當前的實現類型。
// services.AddTransient<DemoTest>();
public class DemoTest : ITransientDependency
{

}

2.2 方法攔截器

2.2.1 ABP vNext 新的抽象層

在 ABP vNext 框架當中,將方法攔截器抽象了一層 IAbpInterceptor,但實際實現仍是使用的 Castle.Core 所提供的動態代理功能,其定義在 Volo.Abp.Dependency.DynamicProxy 文件夾當中,以下圖。

ABP vNext 將攔截器和方法調用模型都進行了定義,其中 AbpInterceptor 則是 IAbpInterceptor 的默認抽象實現。在ProxyHelper 工具類當中,提供了從代理對象獲取真實類型的方法。(PS: 經過 Castle.Core 代理後的對象與原有類型定義是不一致的。)

// ABP vNext 當中的攔截器定義
public interface IAbpInterceptor
{
    // 同步方法攔截。
    void Intercept(IAbpMethodInvocation invocation);

    // 異步方法攔截。
    Task InterceptAsync(IAbpMethodInvocation invocation);
}

// ABP vNext 當中攔截器的默認抽象實現。
public abstract class AbpInterceptor : IAbpInterceptor
{
    public abstract void Intercept(IAbpMethodInvocation invocation);

    // 異步方法本質上仍是調用同步方法,並返回一個已完成的 Task。
    public virtual Task InterceptAsync(IAbpMethodInvocation invocation)
    {
        Intercept(invocation);
        return Task.CompletedTask;
    }
}

至於 IAbpMethodInvocation 接口,則是封裝了一個被攔截方法調用時的各類參數,例如被攔截方法的在調用時所傳遞的參數,返回值類型,方法定義等。而 ABP vNext 也爲它創建了一個 CastleAbpMethodInvocationAdapter 適配器,實現了上述接口。

public interface IAbpMethodInvocation
{
    object[] Arguments { get; }

    IReadOnlyDictionary<string, object> ArgumentsDictionary { get; }

    Type[] GenericArguments { get; }

    object TargetObject { get; }

    MethodInfo Method { get; }

    object ReturnValue { get; set; }

    void Proceed();

    Task ProceedAsync();
}

2.2.2 Castle.Core 動態代理的集成

ABP vNext 在實際使用的時候,仍是經過 Castle.Core 提供的動態代理功能來實現攔截器,相關的代碼存放在 Volo.Abp.Castle.Core 庫和 Volo.Abp.Autofac 庫當中。

首先咱們來看 Castle.Core 庫對接口 IAbpMethodInvocationIAbpInterceptor 的實現,在 CastleAbpInterceptorAdapter 中經過適配器來定義了一個標準的 Castle 攔截器,這個攔截器能夠傳入 ABP vNext 定義的 IAbpInterceptor 做爲其泛型參數。

public class CastleAbpInterceptorAdapter<TInterceptor> : IInterceptor
    where TInterceptor : IAbpInterceptor
{
    
}

Castle 的攔截器也會有一個 Intercept() 方法,該方法將在被攔截方法執行的時候觸發。在觸發以後,會根據當前方法的定義進行不一樣的操做,這裏異步方法和同步方法處理邏輯是不同的。

public void Intercept(IInvocation invocation)
{
    var proceedInfo = invocation.CaptureProceedInfo();

    var method = invocation.MethodInvocationTarget ?? invocation.Method;

    // 判斷執行的方法是不是異步方法。
    if (method.IsAsync())
    {
        InterceptAsyncMethod(invocation, proceedInfo);
    }
    else
    {
        InterceptSyncMethod(invocation, proceedInfo);
    }
}

這裏咱們以異步方法爲例,其內部又會根據方法的返回值是不是 Task 進行不一樣的操做,由於若是是泛型的 Task,說明該異步方法是有返回值的,因此處理邏輯也不同。

private void InterceptAsyncMethod(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
    if (invocation.Method.ReturnType == typeof(Task))
    {
        invocation.ReturnValue = MethodExecuteWithoutReturnValueAsync
            .Invoke(this, new object[] { invocation, proceedInfo });
    }
    else
    {
        invocation.ReturnValue = MethodExecuteWithReturnValueAsync
            .MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0])
            .Invoke(this, new object[] {invocation, proceedInfo});
    }
}

進一步解析在返回類型爲 Task 時,它所調用的方法。

private async Task ExecuteWithoutReturnValueAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
    // 注意這裏,該用法在以前的 C# 多線程學習筆記文章有說過,做用是出讓當前核心給其餘線程。
    await Task.Yield();

    // 調用真實的攔截器,根據傳入的方法調用模型去攔截真實的方法。
    await _abpInterceptor.InterceptAsync(
        new CastleAbpMethodInvocationAdapter(invocation, proceedInfo)
    );
}

從上述代碼能夠得知,ABP vNext 的攔截器動做如今被包裹在一個 Castle 攔截器內部進行的。

那麼,咱們的 Castle.Core 攔截器在何時與類型進行綁定的呢,每一個攔截器又是如何與特性的類型進行註冊的呢?這裏我以審計日誌攔截器爲例,看一下它在系統當中是如何註冊,並被使用的。

審計日誌相關的代碼存放在 Volo.Abp.Auditing 庫中,咱們找到 AuditingInterceptor 類型,查看其定義能夠看到它也是繼承自 AbpInterceptor 抽象基類。

public class AuditingInterceptor : AbpInterceptor, ITransientDependency
{
    
}

接着咱們根據名字找到了攔截器的註冊工具類 AuditingInterceptorRegistrar,在類型的定義當中 ShouldIntercept()ShouldAuditTypeByDefault() 根據傳入的 Type 類型,根據特定的邏輯決定是否爲該類型關聯審計日誌攔截器。

private static bool ShouldIntercept(Type type)
{
    if (ShouldAuditTypeByDefault(type))
    {
        return true;
    }

    // 若是類型的任意方法啓用了 Auditied 特性,則應用攔截器。
    if (type.GetMethods().Any(m => m.IsDefined(typeof(AuditedAttribute), true)))
    {
        return true;
    }

    return false;
}

public static bool ShouldAuditTypeByDefault(Type type)
{
    // 判斷類型是否使用了 Audited 特性,使用了則應用審計日誌攔截器。
    if (type.IsDefined(typeof(AuditedAttribute), true))
    {
        return true;
    }

    // 判斷類型是否使用了 DisableAuditing 特性,使用了則不關聯攔截器。
    if (type.IsDefined(typeof(DisableAuditingAttribute), true))
    {
        return false;
    }

    // 若是類型實現了 IAuditingEnabled 接口,則啓用攔截器。
    if (typeof(IAuditingEnabled).IsAssignableFrom(type))
    {
        return true;
    }

    return false;
}

咱們這裏須要關注的是 RegisterIfNeeded() 方法,它在審計日誌模塊的預加載方法就被添加到了一個 ServiceRegistrationActionList 集合當中,這個集合會在後面 AutoFac 進行類型註冊的時候被使用。

public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
    // 若是類型容許被審計日誌攔截器所攔截,則在類型關聯的攔截器上下文當中添加審計日誌攔截器。
    if (ShouldIntercept(context.ImplementationType))
    {
        context.Interceptors.TryAdd<AuditingInterceptor>();
    }
}
public override void PreConfigureServices(ServiceConfigurationContext context)
{
    // 將這個 Action 加入 List。
    context.Services.OnRegistred(AuditingInterceptorRegistrar.RegisterIfNeeded);
}

繼續查看 OnRegistred() 的代碼,獲得以下的定義,能夠看到最後的 Action 會被添加到一個 ServiceRegistrationActionList 訪問器中。

public static void OnRegistred(this IServiceCollection services, Action<IOnServiceRegistredContext> registrationAction)
{
    GetOrCreateRegistrationActionList(services).Add(registrationAction);
}

public static ServiceRegistrationActionList GetRegistrationActionList(this IServiceCollection services)
{
    return GetOrCreateRegistrationActionList(services);
}

private static ServiceRegistrationActionList GetOrCreateRegistrationActionList(IServiceCollection services)
{
    var actionList = services.GetSingletonInstanceOrNull<IObjectAccessor<ServiceRegistrationActionList>>()?.Value;
    if (actionList == null)
    {
        actionList = new ServiceRegistrationActionList();
        services.AddObjectAccessor(actionList);
    }

    return actionList;
}

AutoFac 在執行註冊操做的時候,會調用 AutofacRegistration 靜態類的 Register 方法,該方法會遍歷整個 IServiceCollection 集合。在將類型註冊到 AutoFac 的 IoC 容器中的時候,在它的內部會調用 AbpRegistrationBuilderExtensions 提供的擴展方法爲具體的類型添加過濾器。

private static void Register(
        ContainerBuilder builder,
        IServiceCollection services)
{
    var moduleContainer = services.GetSingletonInstance<IModuleContainer>();
    // 獲取以前添加的上下文集合,即審計日誌攔截器在預加載方法添加的 Action 集合。
    var registrationActionList = services.GetRegistrationActionList();

    foreach (var service in services)
    {
        if (service.ImplementationType != null)
        {
            var serviceTypeInfo = service.ServiceType.GetTypeInfo();
            if (serviceTypeInfo.IsGenericTypeDefinition)
            {
                builder
                    .RegisterGeneric(service.ImplementationType)
                    .As(service.ServiceType)
                    .ConfigureLifecycle(service.Lifetime)
                    // 這裏是重點,傳入了 Action 集合,調用了擴展方法。
                    .ConfigureAbpConventions(moduleContainer, registrationActionList);
            }
            // ... 註釋了其餘代碼。
        }
        // ... 註釋了其餘代碼。
    }
}

下面是擴展方法所定義的相關代碼,注意閱讀註釋。

public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> ConfigureAbpConventions<TLimit, TActivatorData, TRegistrationStyle>(
        this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder, 
        IModuleContainer moduleContainer, 
        ServiceRegistrationActionList registrationActionList)
    where TActivatorData : ReflectionActivatorData
{
    // ... 註釋了其餘代碼。
    registrationBuilder = registrationBuilder.InvokeRegistrationActions(registrationActionList, serviceType, implementationType);
    // ... 註釋了其餘代碼。
}

private static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> InvokeRegistrationActions<TLimit, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder, ServiceRegistrationActionList registrationActionList, Type serviceType, Type implementationType) 
    where TActivatorData : ReflectionActivatorData
{
    // 構造上下文,以便去調用以前傳入的 Action。
    var serviceRegistredArgs = new OnServiceRegistredContext(serviceType, implementationType);

    foreach (var registrationAction in registrationActionList)
    {
        // 以審計日誌攔截器爲例,這裏會調用在預加載方法傳入的 AuditingInterceptorRegistrar.RegisterIfNeeded 方法。
        registrationAction.Invoke(serviceRegistredArgs);
    }

    // 這裏的 Interceptors 實際上就是 AuditingInterceptorRegistrar.RegisterIfNeeded 內部添加的攔截器哦。
    if (serviceRegistredArgs.Interceptors.Any())
    {
        registrationBuilder = registrationBuilder.AddInterceptors(
            serviceType,
            serviceRegistredArgs.Interceptors
        );
    }

    return registrationBuilder;
}

private static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> AddInterceptors<TLimit, TActivatorData, TRegistrationStyle>(
    this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder, 
    Type serviceType,
    IEnumerable<Type> interceptors)
    where TActivatorData : ReflectionActivatorData
{
    // ... 註釋了其餘代碼。

    foreach (var interceptor in interceptors)
    {
        // 構造真實的攔截器,並與類型集成。
        registrationBuilder.InterceptedBy(
            typeof(CastleAbpInterceptorAdapter<>).MakeGenericType(interceptor)
        );
    }

    return registrationBuilder;
}

2.3 對象訪問器

在第一章節的時候,咱們就遇到過 IObjectAccessor<T> 接口,基本上是針對該接口所提供的 Value 屬性進行操做,下面就是該接口的定義和它的默認實現 ObjectAccessor<T>,十分簡單,就一個泛型的 Value。

public interface IObjectAccessor<out T>
{
    [CanBeNull]
    T Value { get; }
}

public class ObjectAccessor<T> : IObjectAccessor<T>
{
    public T Value { get; set; }

    public ObjectAccessor()
    {
        
    }

    public ObjectAccessor([CanBeNull] T obj)
    {
        Value = obj;
    }
}

僅僅看上述的代碼,是看不出什麼名堂的,接着咱們來到它的擴展方法定義 ServiceCollectionObjectAccessorExtensions

能夠看到其核心的代碼在於 ObjectAccessor<T> AddObjectAccessor<T>(this IServiceCollection services, ObjectAccessor<T> accessor) 這個重載方法。它首先判斷某個特定泛型的對象訪問器是否被註冊,若是被註冊直接拋出異常,沒有則繼續。

最後呢經過一個小技巧,將某個特定類型的對象訪問器做爲單例註冊到 IoC 容器的頭部,方便快速檢索。

public static ObjectAccessor<T> AddObjectAccessor<T>(this IServiceCollection services, ObjectAccessor<T> accessor)
{
    if (services.Any(s => s.ServiceType == typeof(ObjectAccessor<T>)))
    {
        throw new Exception("An object accessor is registered before for type: " + typeof(T).AssemblyQualifiedName);
    }

    //Add to the beginning for fast retrieve
    services.Insert(0, ServiceDescriptor.Singleton(typeof(ObjectAccessor<T>), accessor));
    services.Insert(0, ServiceDescriptor.Singleton(typeof(IObjectAccessor<T>), accessor));

    return accessor;
}

使用的時候,從第一章就有見到,這裏的對象訪問器能夠傳入一個類型。這個時候其 Value 就是空的,但並不影響該類型的解析,只須要在真正使用以前將其 Value 值賦值爲實例對象便可。

只是目前來看,該類型的做用並非十分明顯,更多的時候是一個佔位類型而已,你能夠在任意時間替換某個類型的對象訪問器內部的 Value 值。

2.4 服務的範圍工廠

咱們知道在依賴注入框架當中,有一種特別的生命週期叫作 Scoped 週期,這個週期在我以前的相關文章有講過,它是一個比較特別的生命週期。

簡單來講,Scoped 對象的生命週期只有在某個範圍內是單例存在的,例如如下僞代碼,用戶會請求 ScopedTest() 接口:

public class HomeController()
{
    public Task ScopedTest()
    {
        using(var scope = ScopedFactory.CreateScope<TestApp>())
        {
            scope.ChildContainer.Resolve<TestApp>.Name = "111";
            scope.ChildContainer.Resolve<TestController>();
        }
    }
}

public class TestController()
{
    public TestController(TestApp app)
    {
        Console.WritleLine(app.Name);
    }
}

最後在 TestController 中,控制檯會輸出 111 做爲結果,在 HomeController 中 ScopedTest() 語句塊結束的時候,obj 對象會被釋放,在後續的請求當中,TestApp 都是做爲一個 Scoped 對象生存的。

因此流程能夠分爲如下幾步:

  1. 經過 ScopeFactory 建立一個 Scope 範圍。
  2. 經過 Scope 範圍內的子容器,解析對象。
  3. 子容器在解析時,若是解析出來的類型是 Scope 生命週期,則在整個 Scope 存活期間,它都是單例的
  4. Scope 範圍釋放,會調用銷燬內部的子容器,並銷燬掉全部解析出來的對象。

Volo.Abp.Autofac 庫當中,定義了使用 AutoFac 封裝的範圍工廠與服務範圍類型的定義,他們將會做爲默認的 IServiceScopeFactory 實現。

internal class AutofacServiceScopeFactory : IServiceScopeFactory
{
    private readonly ILifetimeScope _lifetimeScope;

    public AutofacServiceScopeFactory(ILifetimeScope lifetimeScope)
    {
        this._lifetimeScope = lifetimeScope;
    }

    public IServiceScope CreateScope()
    {
        return new AutofacServiceScope(this._lifetimeScope.BeginLifetimeScope());
    }
}

這裏能夠看到,在構建這個工廠的時候,會注入一個 ILifetimScope,這個東西就是 AutoFac 提供的 子容器。在 CreateScope() 方法內部,咱們經過構造一個 Scope 做爲具體的範圍解析對象,並將子容器傳入到它的內部。

internal class AutofacServiceScope : IServiceScope
{
    private readonly ILifetimeScope _lifetimeScope;

    public AutofacServiceScope(ILifetimeScope lifetimeScope)
    {
        // 構造子容器。
        this._lifetimeScope = lifetimeScope;
        this.ServiceProvider = this._lifetimeScope.Resolve<IServiceProvider>();
    }

    public IServiceProvider ServiceProvider { get; }

    public void Dispose()
    {
        // 範圍釋放的時候,釋放子容器。
        this._lifetimeScope.Dispose();
    }
}

那麼是在何時,咱們的範圍工廠會被調用來構造一個 IServiceScope 對象呢?就是在 ASP.NET Core 每次請求的時候,它在得到其內部的 RequestServices 時,就會經過 IServiceProvidersFeature 來建立一個 Scope 範圍。

public IServiceProvider RequestServices
{
    get
    {
        if (!_requestServicesSet)
        {
            _context.Response.RegisterForDispose(this);
            // 經過工廠,建立一個範圍解析對象,這裏就是 AutofacServiceScopeFactory。
            _scope = _scopeFactory.CreateScope();
            _requestServices = _scope.ServiceProvider;
            _requestServicesSet = true;
        }
        return _requestServices;
    }

    set
    {
        _requestServices = value;
        _requestServicesSet = true;
    }
}

因此,咱們在每次請求的時候,針對於 Scope 聲明週期的對象,默認的話都是在整個請求處理期間,都是單例的,除非顯式使用 using 語句塊聲明做用域。

而在 ABP vNext 中給咱們提供了兩個 Scoped Factory,分別是 HttpContextServiceScopeFactoryDefaultServiceScopeFactory ,它們都繼承自 IHybridServiceScopeFactory 接口。

這個 IHybridServiceScopeFactory 接口只是一個空的接口,並繼承自 Microsoft Dependency Inject 提供的 IServiceScopeFactory 工廠接口。

但在實際注入的時候,並不會替換掉默認的 IServiceScopeFactory 實現。由於在 IHybridServiceScopeFactory 的默認兩個實現的定義上,他們都顯式得經過 ExposeServices 特性說明了本身是哪些類型的默認實現,且通常使用的時候,都是經過注入 IHybridServiceScopeFactory 並結合 using 語句塊來操做。

例如在 Volo.Abp.Data 庫的 DataSeeder 類型中,有以下用法。

public async Task SeedAsync(DataSeedContext context)
{
    using (var scope = ServiceScopeFactory.CreateScope())
    {
        foreach (var contributorType in Options.Contributors)
        {
            var contributor = (IDataSeedContributor) scope
                .ServiceProvider
                .GetRequiredService(contributorType);

            await contributor.SeedAsync(context);
        }
    }
}

只是這兩個實現有什麼不一樣呢?經過兩個類型的名字就能夠看出來,一個是給 ASP.NET Core MVC 程序使用的,另外一個則是默認的範圍工廠,下面咱們從代碼層面上來比較一下二者之間的差異。

[ExposeServices(
    typeof(IHybridServiceScopeFactory), 
    typeof(DefaultServiceScopeFactory)
    )]
public class DefaultServiceScopeFactory : IHybridServiceScopeFactory, ITransientDependency
{
    // 直接注入封裝的 AutofacServiceScopeFactory。
    protected IServiceScopeFactory Factory { get; }

    public DefaultServiceScopeFactory(IServiceScopeFactory factory)
    {
        Factory = factory;
    }

    public IServiceScope CreateScope()
    {
        // 經過 AutofacServiceScopeFactory 建立一個 scope。
        return Factory.CreateScope();
    }
}

HttpContextServiceScopeFactory 是放在 AspNetCore 模塊下的,從他的 Dependency 特性能夠看出來,他會替換掉默認的 DefaultServiceScopeFactory 實現。

[ExposeServices(
    typeof(IHybridServiceScopeFactory),
    typeof(HttpContextServiceScopeFactory)
    )]
[Dependency(ReplaceServices = true)]
public class HttpContextServiceScopeFactory : IHybridServiceScopeFactory, ITransientDependency
{
    protected IHttpContextAccessor HttpContextAccessor { get; }

    // AutoFacServiceScopeFactory
    protected IServiceScopeFactory ServiceScopeFactory { get; }

    public HttpContextServiceScopeFactory(
        IHttpContextAccessor httpContextAccessor, 
        IServiceScopeFactory serviceScopeFactory)
    {
        HttpContextAccessor = httpContextAccessor;
        ServiceScopeFactory = serviceScopeFactory;
    }

    public virtual IServiceScope CreateScope()
    {
        // 假如 HTTP 上下文爲空,直接使用 AutoFacScopeFactory 建立一個範圍。
        var httpContext = HttpContextAccessor.HttpContext;
        if (httpContext == null)
        {
            return ServiceScopeFactory.CreateScope();
        }

        // 使用 HttpContext 的 RequestServices 構建一個 Scope。
        return new NonDisposedHttpContextServiceScope(httpContext.RequestServices);
    }

    protected class NonDisposedHttpContextServiceScope : IServiceScope
    {
        public IServiceProvider ServiceProvider { get; }

        public NonDisposedHttpContextServiceScope(IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
        }

        public void Dispose()
        {
            
        }
    }
}

能夠看到,後者若是在 HttpContext 不爲 null 的時候,是使用的 HttpContext.RequestServices 做爲這個 Scope 的解析器。

RequestServices, on the other hand, is a scoped container created from the root on each request.

翻譯成中文的意思就是,它是在每一個請求的的時候建立的獨立範圍容器,其實就是開頭所說的子容器。

2.5 類型註冊完成的動做

其實這個玩意兒應該放在 2.2 節以前講,只是在寫完以後我纔看到相關類型是放在依賴注入相關的文件夾當中,這裏還請各位讀者理解一下。

早期在 Castle Windsor 當中,類型在註冊完成的時候會有一個註冊完成的事件,用戶能夠掛載該事件來進行一些特殊的處理,好比說爲類型添加動態代理。在 ABP vNext 當中由於支持多種不一樣的依賴注入框架,因此就沒有相似的事件來作處理。

ABP vNext 則封裝了一個 ServiceRegistrationActionList 類型,該類型用於存儲在類型註冊完成以後,用戶能夠執行的操做,能夠看到它就是一個 Action 集合,用於存放一系列回調方法。

public class ServiceRegistrationActionList : List<Action<IOnServiceRegistredContext>>
{
    
}

由 2.2 節得知,這個玩意兒是在每個類型註冊完成以後,都會被遍歷調用其中的 Action 動做。在調用的時候,會將當前註冊完成的類型封裝成一個 IOnServiceRegistredContext 對象,傳遞給具體的委託,這樣委託就可以知道當前調用的類型,也就可以將攔截器放在其 Interceptors 屬性當中了。

public interface IOnServiceRegistredContext
{
    ITypeList<IAbpInterceptor> Interceptors { get; }

    Type ImplementationType { get; }
}

3、總結

ABP vNext 框架針對於依賴注入這塊的工做也進行了大量的精簡,就代碼量來講,比原有 ABP 框架減小了差很少一半左右,並且整個邏輯也比原來更加簡潔易懂。

開發人員在使用的時候,其實最多的是關注如何注入本身想要的類型。經過了解 ABP vNext 底層的代碼, 方便咱們清楚攔截器和依賴注入框架的具體過程,這樣在後面擴展功能的時候纔可以作到心中有數。

4、點擊我跳轉到文章目錄

相關文章
相關標籤/搜索