ABP 攔截器

ABP 大量採用 AOP 設計將橫切關注點抽離到攔截器中,保持程式碼乾淨且易於維護。 底層利用 Castle Core 整合 Autofac 實現 AOP,但 ABP 為了避免直接依賴第三方函式庫,額外封裝了一層抽象,以便未來擴充。

Castle Core 與整合 Autofac 的基礎知識可以參考之前的文章:




核心概念

以下是幾個關鍵的知識點:

  1. 函式庫依賴
    • Castle.Core:提供 DynamicProxy,實現攔截功能。
    • Castle.Core.AsyncInterceptor:簡化非同步攔截器實作。
    • Autofac.Extras.DynamicProxy:將 Castle DynamicProxy 整合進 Autofac DI。
    • Autofac.Extensions.DependencyInjection:使用 Autofac 替代原生 DI。
  2. 攔截器流程
    • Castle.Core 攔截器呼叫 invocation.Proceed() 回到原流程並執行原始方法。
    • Castle.Core.AsyncInterceptor 建立的非同步攔截器透過 AsyncDeterminationInterceptorIAsyncInterceptor 包裝成 IInterceptor
    • AsyncDeterminationInterceptor 內部會自動判斷攔截的方法是 SynchronousAsyncActionAsyncFunction 三個其中一種,並呼叫對應攔截程式。
    • Autofac.Extras.DynamicProxy 只看得懂 IInterceptor 介面。
    • EnableInterfaceInterceptors()EnableClassInterceptors() 分別用於介面與類別的攔截。
    • InterceptedBy 需要指定獨立類別名稱以區分不同攔截器。
  3. 非同步攔截器實作方式(底層都是實作 IAsyncInterceptor)
    • 實作 IAsyncInterceptor
    • 繼承 AsyncInterceptorBase
    • 繼承 ProcessingAsyncInterceptor<TState>




建立 ABP 攔截器

首先參考一下內建的 UnitOfWorkInterceptor 攔截器,它用來將當前請求包裝成一個事務:

  1. 繼承 AbpInterceptor 並註冊為 Transient。
  2. 建立 RegisterIfNeeded 委派,透過 ShouldIntercept() 方法,判斷是否將此攔截器加入 OnServiceRegistredContext.Interceptors清單。
  3. 所有攔截器的 RegisterIfNeeded 委派集中到 ServiceRegistrationActionList,程式啟動時透過 UseAutofac() 方法依序執行所有委派完成註冊。



UnitOfWorkInterceptor 核心程式碼

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;

namespace Volo.Abp.Uow;

public class UnitOfWorkInterceptor : AbpInterceptor, ITransientDependency
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public UnitOfWorkInterceptor(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    public override async Task InterceptAsync(IAbpMethodInvocation invocation)
    {
        if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute))
        {
            await invocation.ProceedAsync();
            return;
        }

        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var options = CreateOptions(scope.ServiceProvider, invocation, unitOfWorkAttribute);

            var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();

            //Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware
            if (unitOfWorkManager.TryBeginReserved(UnitOfWork.UnitOfWorkReservationName, options))
            {
                await invocation.ProceedAsync();

                if (unitOfWorkManager.Current != null)
                {
                    await unitOfWorkManager.Current.SaveChangesAsync();
                }

                return;
            }

            using (var uow = unitOfWorkManager.Begin(options))
            {
                await invocation.ProceedAsync();
                await uow.CompleteAsync();
            }
        }
    }

    private AbpUnitOfWorkOptions CreateOptions(IServiceProvider serviceProvider, IAbpMethodInvocation invocation, UnitOfWorkAttribute? unitOfWorkAttribute)
    {
        var options = new AbpUnitOfWorkOptions();

        unitOfWorkAttribute?.SetOptions(options);

        if (unitOfWorkAttribute?.IsTransactional == null)
        {
            var defaultOptions = serviceProvider.GetRequiredService<IOptions<AbpUnitOfWorkDefaultOptions>>().Value;
            options.IsTransactional = defaultOptions.CalculateIsTransactional(
                autoValue: serviceProvider.GetRequiredService<IUnitOfWorkTransactionBehaviourProvider>().IsTransactional
                           ?? !invocation.Method.Name.StartsWith("Get", StringComparison.InvariantCultureIgnoreCase)
            );
        }

        return options;
    }
}



攔截器註冊

using System;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;

namespace Volo.Abp.Uow;

public static class UnitOfWorkInterceptorRegistrar
{
    public static void RegisterIfNeeded(IOnServiceRegistredContext context)
    {
        if (ShouldIntercept(context.ImplementationType))
        {
            context.Interceptors.TryAdd<UnitOfWorkInterceptor>(); // 加入到 `OnServiceRegistredContext` 的 `Interceptors` 清單中
        }
    }

    private static bool ShouldIntercept(Type type)
    {
        return !DynamicProxyIgnoreTypes.Contains(type) && UnitOfWorkHelper.IsUnitOfWorkType(type.GetTypeInfo());
    }
}
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;

namespace Volo.Abp.Uow;

public class AbpUnitOfWorkModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.OnRegistered(UnitOfWorkInterceptorRegistrar.RegisterIfNeeded);
    }
}




註冊流程分析

UseAutofac() 執行的過程中,透過 AbpRegistrationBuilderExtensionsInvokeRegistrationActionsAddInterceptors 擴充方法將攔截器註冊到容器:

  • 若服務是介面,呼叫 EnableInterfaceInterceptors()
  • 若服務是類別,呼叫 EnableClassInterceptors()
  • InterceptedBy 註冊的攔截器為泛型類別 AbpAsyncDeterminationInterceptor<TInterceptor>, 因此最終註冊的形式為 AbpAsyncDeterminationInterceptor<UnitOfWorkInterceptor>, 負責將 IAsyncInterceptor 轉接成 IInterceptor
private static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> InvokeRegistrationActions<TLimit, TActivatorData, TRegistrationStyle>(
        this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder,
        ServiceRegistrationActionList registrationActionList,
        Type serviceType,
        Type implementationType,
        object? serviceKey = null)
        where TActivatorData : ReflectionActivatorData
    {
        var serviceRegistredArgs = new OnServiceRegistredContext(serviceType, implementationType, serviceKey);

        foreach (var registrationAction in registrationActionList)
        {
            registrationAction.Invoke(serviceRegistredArgs);
        }

        if (serviceRegistredArgs.Interceptors.Any())
        {
            var disableAbpFeaturesAttribute = serviceRegistredArgs.ImplementationType.GetCustomAttribute<DisableAbpFeaturesAttribute>(true);
            if (disableAbpFeaturesAttribute == null || !disableAbpFeaturesAttribute.DisableInterceptors)
            {
                registrationBuilder = registrationBuilder.AddInterceptors(
                    registrationActionList,
                    serviceType,
                    serviceRegistredArgs.Interceptors
                );
            }
        }

        return registrationBuilder;
    }

private static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle>
        AddInterceptors<TLimit, TActivatorData, TRegistrationStyle>(
            this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder,
            ServiceRegistrationActionList serviceRegistrationActionList,
            Type serviceType,
            IEnumerable<Type> interceptors)
        where TActivatorData : ReflectionActivatorData
    {
        if (serviceType.IsInterface)
        {
            registrationBuilder = registrationBuilder.EnableInterfaceInterceptors();
        }
        else
        {
            if (serviceRegistrationActionList.IsClassInterceptorsDisabled ||
                serviceRegistrationActionList.DisabledClassInterceptorsSelectors.Any(selector => selector.Predicate(serviceType)))
            {
                return registrationBuilder;
            }

            (registrationBuilder as IRegistrationBuilder<TLimit, ConcreteReflectionActivatorData, TRegistrationStyle>)?.EnableClassInterceptors();
        }

        foreach (var interceptor in interceptors)
        {
            registrationBuilder.InterceptedBy(
                typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(interceptor)
            );
        }

        return registrationBuilder;
    }



轉接層

AbpAsyncDeterminationInterceptor 是 ABP 建立的抽象封裝,繼承的類別是之前提到過的 AsyncDeterminationInterceptor 關鍵類別, 能夠轉接成 IInterceptor 攔截器。

這裡將 CastleAsyncAbpInterceptorAdapter<TInterceptor> 將 ABP 攔截器轉接給 Castle:

using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;

namespace Volo.Abp.Castle.DynamicProxy;

public class AbpAsyncDeterminationInterceptor<TInterceptor> : AsyncDeterminationInterceptor
    where TInterceptor : IAbpInterceptor
{
    public AbpAsyncDeterminationInterceptor(TInterceptor abpInterceptor)
        : base(new CastleAsyncAbpInterceptorAdapter<TInterceptor>(abpInterceptor))
    {

    }
}

CastleAsyncAbpInterceptorAdapter 則是繼承 AsyncInterceptorBase 類別,我們已經知道它的底層就是 IAsyncInterceptor 介面, 這個轉接層就是 Castle 攔截器呼叫 ABP Castle 攔截器的地方,也就是開始執行 UnitOfWorkInterceptorInterceptAsync 方法。

using System;
using System.Threading.Tasks;
using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;

namespace Volo.Abp.Castle.DynamicProxy;

public class CastleAsyncAbpInterceptorAdapter<TInterceptor> : AsyncInterceptorBase
    where TInterceptor : IAbpInterceptor
{
    private readonly TInterceptor _abpInterceptor;

    public CastleAsyncAbpInterceptorAdapter(TInterceptor abpInterceptor)
    {
        _abpInterceptor = abpInterceptor;
    }

    protected override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task> proceed)
    {
        await _abpInterceptor.InterceptAsync(
            new CastleAbpMethodInvocationAdapter(invocation, proceedInfo, proceed)
        );
    }

    protected override async Task<TResult> InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed)
    {
        var adapter = new CastleAbpMethodInvocationAdapterWithReturnValue<TResult>(invocation, proceedInfo, proceed);

        await _abpInterceptor.InterceptAsync(
            adapter
        );

        return (TResult)adapter.ReturnValue;
    }
}

另外 ABP 為了避免直接依賴 Castle Core 的 invocation.Proceed() 方法呼叫原始流程,所以引入了 IAbpMethodInvocation 介面

透過 CastleAbpMethodInvocationAdapter 類別將原本 Castle Core 的資訊包裝成 ABP 專用的 IAbpMethodInvocation 介面

因此 UnitOfWorkInterceptor 只需要呼叫 invocation.ProceedAsync() 背後就會透過轉接層呼叫 Castle Core 的 invocation.Proceed()

using System;
using System.Threading.Tasks;
using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;

namespace Volo.Abp.Castle.DynamicProxy;

public class CastleAbpMethodInvocationAdapter : CastleAbpMethodInvocationAdapterBase, IAbpMethodInvocation
{
    protected IInvocationProceedInfo ProceedInfo { get; }
    protected Func<IInvocation, IInvocationProceedInfo, Task> Proceed { get; }

    public CastleAbpMethodInvocationAdapter(IInvocation invocation, IInvocationProceedInfo proceedInfo,
        Func<IInvocation, IInvocationProceedInfo, Task> proceed)
        : base(invocation)
    {
        ProceedInfo = proceedInfo;
        Proceed = proceed;
    }

    public override async Task ProceedAsync()
    {
        await Proceed(Invocation, ProceedInfo);
    }
}




Summary

ABP 的 AOP 流程大致如下:

  1. 當呼叫 MyService.DoWorkAsync() 時,由於應用程式啟動階段已透過 Autofac 將所有攔截器預先綁定到服務上, 此時實際被呼叫的物件並非 MyService 本身,而是 Castle DynamicProxy 產生的 Proxy 物件。
  2. Proxy 會依序執行已註冊的攔截器,其中包含 AbpAsyncDeterminationInterceptor<UnitOfWorkInterceptor>
  3. 由於 AbpAsyncDeterminationInterceptor<UnitOfWorkInterceptor> 繼承自 AsyncDeterminationInterceptor, 它會在內部判斷目前呼叫的 MyService.DoWorkAsync() 是非同步方法,並自動轉交給 CastleAsyncAbpInterceptorAdapterInterceptAsync 方法處理。
  4. CastleAsyncAbpInterceptorAdapter 透過依賴注入取得 UnitOfWorkInterceptor, 將 Castle 的 IInvocationProceedInfo 資訊封裝成 ABP 專用的 CastleAbpMethodInvocationAdapter, 再呼叫 UnitOfWorkInterceptor.InterceptAsync(CastleAbpMethodInvocationAdapter)
  5. UnitOfWorkInterceptor.InterceptAsync 先執行前置邏輯(開始事務), 接著呼叫 invocation.ProceedAsync(),流程便會回到 Proxy 物件,執行真正的 MyService.DoWorkAsync() 方法。
  6. 當原始方法執行完成後,流程會再返回 UnitOfWorkInterceptor.InterceptAsync,執行後置邏輯(提交或回滾交易), 最後將結果一路回傳給的呼叫端。
Client
   |
   v
呼叫 MyService.DoWorkAsync()
   |
   v
IMyService 介面與攔截器在程式啟動時已經綁定好
   |
   v
AbpAsyncDeterminationInterceptor<UnitOfWorkInterceptor>
   |
   v
CastleAsyncAbpInterceptorAdapter<UnitOfWorkInterceptor>
   |
   v
UnitOfWorkInterceptor.InterceptAsync
┌──────────────────────────┐
│ 前置邏輯(開始事務)          │
│ invocation.ProceedAsync()│ -> MyService.DoWorkAsync()
│ 後置邏輯(提交/回滾)         │
└──────────────────────────┘
   |
   v
結果回傳給 Client