動手造輪子:實現一個簡單的 AOP 框架

WeihanLi發表於2020-06-14

動手造輪子:實現一個簡單的 AOP 框架

Intro

最近實現了一個 AOP 框架 -- FluentAspects,API 基本穩定了,寫篇文章分享一下這個 AOP 框架的設計。

整體設計

概覽

IProxyTypeFactory

用來生成代理型別,預設提供了基於 Emit 動態代理的實現,基於介面設計,可以擴充套件為其他實現方式

介面定義如下:

public interface IProxyTypeFactory
{
    Type CreateProxyType(Type serviceType);

    Type CreateProxyType(Type serviceType, Type implementType);
}

IProxyFactory

用來生成代理例項,預設實現是基於 IProxyTypeFactory 生成代理型別之後建立例項

介面定義如下:

public interface IProxyFactory
{
    object CreateProxy(Type serviceType, object[] arguments);

    object CreateProxy(Type serviceType, Type implementType, params object[] arguments);

    object CreateProxyWithTarget(Type serviceType, object implement, object[] arguments);
}

IInvocation

執行上下文,預設實現就是方法執行的上下文,包含了代理方法資訊、被代理的方法資訊、方法引數,返回值以及用來自定義擴充套件的一個 Properties 屬性

public interface IInvocation
{
    MethodInfo ProxyMethod { get; }

    object ProxyTarget { get; }

    MethodInfo Method { get; }

    object Target { get; }

    object[] Arguments { get; }

    Type[] GenericArguments { get; }

    object ReturnValue { get; set; }

    Dictionary<string, object> Properties { get; }
}

IInterceptor

攔截器,用來定義公用的處理邏輯,方法攔截處理方法

介面定義如下:

public interface IInterceptor
{
    Task Invoke(IInvocation invocation, Func<Task> next);
}

invocation 是方法執行的上下文,next 代表後續的邏輯處理,類似於 asp.net core 裡的 next ,如果不想執行方面的方法不執行 next 邏輯即可

IInterceptorResolver

用來根據當前的執行上下文獲取到要執行的攔截器,預設是基於 FluentAPI 的實現,但是如果你特別想用基於 Attribute 的也是可以的,預設提供了一個 AttributeInterceotorResovler,你也可以自定義一個適合自己的 InterceptorResolver

public interface IInterceptorResolver
{
    IReadOnlyList<IInterceptor> ResolveInterceptors(IInvocation invocation);
}

IInvocationEnricher

上面 IInvocation 的定義中有一個用於擴充套件的 Properties,這個 enricher 主要就是基於 Properties 來豐富執行上下文資訊的,比如說記錄 TraceId 等請求鏈路追蹤資料,構建方法執行鏈路等

public interface IEnricher<in TContext>
{
    void Enrich(TContext context);
}
public interface IInvocationEnricher : IEnricher<IInvocation>
{
}

AspectDelegate

AspectDelegate 是用來將構建要執行的代理方法的方法體的,首先執行註冊的 InvocationEnricher,豐富上下文資訊,然後根據執行上下文獲取要執行的攔截器,構建一個執行委託,生成委託使用了之前分享過的 PipelineBuilder 構建中介軟體模式的攔截器,執行攔截器邏輯

// apply enrichers
foreach (var enricher in FluentAspects.AspectOptions.Enrichers)
{
    try
    {
        enricher.Enrich(invocation);
    }
    catch (Exception ex)
    {
        InvokeHelper.OnInvokeException?.Invoke(ex);
    }
}

// get delegate
var builder = PipelineBuilder.CreateAsync(completeFunc);
foreach (var interceptor in interceptors)
{
    builder.Use(interceptor.Invoke);
}
return builder.Build();

更多資訊可以參考原始碼: https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Aspect/AspectDelegate.cs

使用示例

推薦和依賴注入結合使用,主要分為以微軟的注入框架為例,有兩種使用方式,一種是手動註冊代理服務,一種是自動批量註冊代理服務,來看下面的例項就明白了

手動註冊代理服務

使用方式一,手動註冊代理服務:

為了方便使用,提供了一些 AddProxy 的擴充套件方法:

IServiceCollection services = new ServiceCollection();
services.AddFluentAspects(options =>
    {
        // 註冊攔截器配置
        options.NoInterceptProperty<IFly>(f => f.Name);

        options.InterceptAll()
            .With<LogInterceptor>()
            ;
        options.InterceptMethod<DbContext>(x => x.Name == nameof(DbContext.SaveChanges)
                                                || x.Name == nameof(DbContext.SaveChangesAsync))
            .With<DbContextSaveInterceptor>()
            ;
        options.InterceptMethod<IFly>(f => f.Fly())
            .With<LogInterceptor>();
        options.InterceptType<IFly>()
            .With<LogInterceptor>();

        // 註冊 InvocationEnricher
        options
            .WithProperty("TraceId", "121212")
            ;
    });
// 使用 Castle 生成代理
services.AddFluentAspects(options =>
    {
        // 註冊攔截器配置
        options.NoInterceptProperty<IFly>(f => f.Name);

        options.InterceptAll()
            .With<LogInterceptor>()
            ;
        options.InterceptMethod<DbContext>(x => x.Name == nameof(DbContext.SaveChanges)
                                                || x.Name == nameof(DbContext.SaveChangesAsync))
            .With<DbContextSaveInterceptor>()
            ;
        options.InterceptMethod<IFly>(f => f.Fly())
            .With<LogInterceptor>();
        options.InterceptType<IFly>()
            .With<LogInterceptor>();

        // 註冊 InvocationEnricher
        options
            .WithProperty("TraceId", "121212")
            ;
    }, builder => builder.UseCastle());

services.AddTransientProxy<IFly, MonkeyKing>();
services.AddSingletonProxy<IEventBus, EventBus>();
services.AddDbContext<TestDbContext>(options =>
{
    options.UseInMemoryDatabase("Test");
});
services.AddScopedProxy<TestDbContext>();

var serviceProvider = services.BuildServiceProvider();

批量自動註冊代理服務

使用方式二,批量自動註冊代理服務:

IServiceCollection services = new ServiceCollection();
services.AddTransient<IFly, MonkeyKing>();
services.AddSingleton<IEventBus, EventBus>();
services.AddDbContext<TestDbContext>(options =>
{
    options.UseInMemoryDatabase("Test");
});

var serviceProvider = services.BuildFluentAspectsProvider(options =>
            {
                options.InterceptAll()
                    .With<TestOutputInterceptor>(output);
            });

// 使用 Castle 來生成代理
var serviceProvider = services.BuildFluentAspectsProvider(options =>
            {
                options.InterceptAll()
                    .With<TestOutputInterceptor>(output);
            }, builder => builder.UseCastle());

// 忽略名稱空間為 Microsoft/System 的服務型別
var serviceProvider = services.BuildFluentAspectsProvider(options =>
            {
                options.InterceptAll()
                    .With<TestOutputInterceptor>(output);
            }, builder => builder.UseCastle(), t=> t.Namespace != null && (t.Namespace.StartWith("Microsft") ||t.Namespace.StartWith("Microsft")));

More

上面的兩種方式個人比較推薦使用第一種方式,需要攔截什麼就註冊什麼代理服務,自動註冊可能會生成很多不必要的代理服務,個人還是比較喜歡按需註冊的方式。

這個框架還不是很完善,有一些地方還是需要優化的,目前還是在我自己的類庫中,因為我的類庫裡要支援 net45,所以有一些不好的設計改起來不太方便,打算遷移出來作為一個單獨的元件,直接基於 netstandard2.0/netstandard2.1, 甩掉 netfx 的包袱。

Reference

相關文章