C# 实现 AOP 面向切面编程

前言

简单记录一下对AOP的认识,正文为3个部分

AOP由来

用DispatchProxy动态代理实现AOP

通过特性标记,处理多种不同执行前、执行后的逻辑编排.

一、AOP 由来

IUserHelper userHelper = new CommonUserHelper();    // commonUser.Create中存在 方法执行前、方法执行后的业务逻辑
userHelper.Create("test0401_A");


public interface IUserHelper
{
    void Create(string name);
}

public class CommonUserHelper : IUserHelper
{
    private void before()
    {
        Console.WriteLine("CommonUser before");
    }

    private void after()
    {
        Console.WriteLine("CommonUser after");
    }

    public void Create(string name)
    {
        before();
        Console.WriteLine($"  Common User : {name} Created !");
        after();
    }
}

CommonUserHelper 实现 IUserHelper 接口,假设希望在 Create方法执行前/后写入日志,那就存在这4种业务逻辑:

1、执行前写入日志,执行 Create

2、执行前写入日志,执行 Create,执行后写入日志

3、执行 Create,执行后写入日志

4、执行 Create

单一个写日志的需求,就能有4种实现方式,极端情况下,是可以实现 4次 Create 方法;

如果再加一个数据验证、IP验证、权限验证、异常处理、加入缓存..,那么实现的排列组合方式就更多了,

无穷尽地加实现、替换类,这显然不是我们希望的。

AOP,Aspect Oriented Programing,是一种编程思维,是对这种缺陷的补充。

二、DispatchProxy (动态代理)实现AOP

using System.Reflection;
namespace Cjm.AOP
{
    public class TransformProxy
    {
        public static T GetDynamicProxy<T>(T instance)  
        {
            // DispatchProxy 是system.Reflection封装的类
            // 用以创建实现接口T的代理类CustomProxy的实例
            dynamic obj = DispatchProxy.Create<T, CustomProxy<T>>();
            obj.Instance = instance;
            return (T)obj;
        }
    }

    // DispatchProxy 是抽象类,
    // 实现该类的实例,实例方法执行是会跳转到 Invoke 方法中,
    // 以此达到不破坏实际执行的具体逻辑,而又可以在另外的地方实现执行前、执行后
    public class CustomProxy<T> : DispatchProxy
    {
        public T Instance { get; set; }
        protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
        {
            BeforeProcess();
            var relt = targetMethod?.Invoke(Instance, args);
            AfterProcess();
            return relt;
        }

        private void BeforeProcess()
        {
            Console.WriteLine($"This is BegoreProcess.");
        }

        private void AfterProcess()
        {
            Console.WriteLine($"This is AfterProcess.");
        }
    }
}

// Main
IUserHelper userHelper3 = new CommonUserHelper();
userHelper3 = TransformProxy.GetDynamicProxy(userHelper3);
userHelper3.Create("test0401_B");

三、通过标记特性,处理多种不同的执行前/执行后方法

此处借用Castle.Core的封装(可通过Nuget管理下载),通过实现 StandardInterceptor以重写 执行前/执行后 逻辑的封装方式,我们可以更加聚焦在如何处理多种 执行前/执行后 逻辑的编排上。

using Castle.DynamicProxy;
{
    ProxyGenerator proxy = new ProxyGenerator();
    CustomInterceptor customInterceptor = new CustomInterceptor();
    IUserHelper commonUserHelper = new CommonUserHelper();
    var userHelperProxy = proxy.CreateInterfaceProxyWithTarget<IUserHelper>(commonUserHelper, customInterceptor);
    userHelperProxy.Create("TEST0401_C");
}    
public class CustomInterceptor : StandardInterceptor
{
    protected override void PreProceed(IInvocation invocation)
    {
        var method = invocation.Method;
        //if (method.IsDefined(typeof(LogBeforeAttribute), true))
        //{
        //    Console.WriteLine("LOG : CustomInterceptor.PreProceed");
        //}

        Action<IInvocation> action = (invocation) => base.PreProceed(invocation);
        // 获取该方法的所有继承BaseAOPAttribute的特性
        var attrs = method.GetCustomAttributes<BaseAOPAttribute>(true);            // 对于 attrs 的排列顺序,可以在特性的实现中增加 int order 属性,在标记特性时写入排序编号
        foreach(var attr in attrs)
        {
            // 这里是俄罗斯套娃
            // 相当于 attr3.AOPAction(invocation, attr2.AOPAction(invocation, attr1.AOPAction(invocation, base.PreProceed(invocation))))
            action = attr.AOPAction(invocation, action);
        }
        action.Invoke(invocation);
    }

    protected override void PerformProceed(IInvocation invocation)
    {
        Console.WriteLine("CustomInterceptor.PerformProceed");
        base.PerformProceed(invocation);
    }

    protected override void PostProceed(IInvocation invocation)
    {
        var method = invocation.Method;
        if (method.IsDefined(typeof(LogAfterAttribute), true))
        {
            Console.WriteLine("LOG : CustomInterceptor.PostProceed");
        }

        base.PreProceed(invocation);
    }
}
public class LogBeforeAttribute : Attribute {}

public class LogAfterAttribute : Attribute {}

public class CheckIPAttribute : BaseAOPAttribute
{
    public override Action<IInvocation> AOPAction(IInvocation invocation, Action<IInvocation> action)
    {
        return (invocation) => {
            Console.WriteLine("CheckIP ..");
            action.Invoke(invocation);             };
    }
}

public abstract class BaseAOPAttribute : Attribute
{
    public abstract Action<IInvocation> AOPAction(IInvocation invocation, Action<IInvocation> action);
}

通过给方法标记特性的方式,达到切面编程的目的(不影响原有实现,而增加实现执行前/执行后的逻辑)

public interface IUserHelper
{
    [LogBefore]
    [LogAfter]
    [CheckIP]
    void Create(string name);

    void CreateNoAttri();
}

具体的AOP实现上需要考虑的问题多如牛毛,此处仅做简单的思路介绍。