• 动手造轮子:实现一个简单的 AOP 框架


    动手造轮子:实现一个简单的 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

  • 相关阅读:
    PHP PSR 标准规范
    PHP线程安全与非线程安全的区别(NTS/TS)选择?
    goto 语法在 PHP 中的使用
    linux命令post请求发送json串
    接口日志表sql【我】
    sentinel最简单接入【我】
    Jps命令—使用详解
    MyBatis-Plus Wrapper条件构造器查询大全
    IDEA项目启动不起来也不打印日志【我】
    linux下查看nginx配置文件地址
  • 原文地址:https://www.cnblogs.com/weihanli/p/13126235.html
Copyright © 2020-2023  润新知