单从Advice(通知)实现AOP

时间:2023-03-08 15:38:24
单从Advice(通知)实现AOP

如果你在实际开发中没感觉到OOP的一些缺陷,就不要往下看了!

如果你不了解AOP,或类似AOP的思路,请先去了解一下AOP相关的认识。

如果你是概念党,或是经验党,或是从众党,也请不要看了!

我实现的只是一个小功能,是不是AOP我并不清楚,也不主要,标题那样写只是让大家一看就明白本文讲的大概是什么,况且我也想不出写个什么标题。

因为我看了别人谈论的AOP,扯到好多东西,代理啦、emit啦等等天花乱坠的,不过一个也没看懂(I hate complex)!

但有一点对我很有用,就是几种通知类型:

1,目标方法调用前(before)

2,目标方法调用后(after)---目标方法异常也执行

3,目标方法返回后(after return)---目标方法异常不执行

3,目标方法调用前后(around)

4,目标方法抛出异常时(throw)

我感觉稍微整理一下更好理解,所以下方的实现是基于这6种类型的:

1,目标方法调用前(Before)

2,目标方法调用后(After)--目标方法异常是不执行的!

3,目标方法调用后(AfterEnsure) --目标方法异常也执行。

4,目标方法调用前后(Around)--目标方法异常,After是不执行的

5,目标方法调用前后(AroundEnsure)--目标方法异常,After也执行

6,目标方法抛出异常时(Throw)

考虑到多种通知处理,就需要一个统一接口(泛指接口), 而这里抽象类比.net接口更合适:(这个组件只有两个类,这是其中之一)

    public abstract class AspectAdvice
    {
        public virtual void Before(object target, MethodInfo mi, params object[] args) { }
        public virtual void After(object target, MethodInfo mi, params object[] args) { }
        public virtual void Throw(object target, MethodInfo mi, params object[] args) { }
    }

三个方法就够了,这三个方法与目标方法不同的调用逻辑可以满足上面6种通知,下面再分析。

为了好描述,我也以日志记录为例,下面分别是目标类,和日志类(可以叫它切面吗?呵呵)

    public class MyClass
    {
        //无返回值
        public void Act()
        {
            Console.WriteLine("MyClass.Act()");
        }
        //有返回值
        public int Fun(string str)
        {
            return str.Length;
        }
        ;
        //用到对象成员m_num,可能发生异常
        public int Divide(int n)
        {
            return m_num / n;
        }
        //静态方法
        public static void SFun(string str)
        {
            Console.WriteLine(str);
        }
    }

通知处理类都要实现AspectAdvice, 如日志类:

    public class Log : AspectAdvice
    {
        public override void Before(object target, MethodInfo mi, params object[] args)
        {
            Console.WriteLine("===========Log Before===========");
        }
        public override void After(object target, MethodInfo mi, params object[] args)
        {
            Console.WriteLine("===========Log After===========");
        }
        public override void Throw(object target, MethodInfo mi, params object[] args)
        {
            Console.WriteLine("===========Log Exception===========");
        }

        private Log() { }
        static Log m_instance;
        public static Log Instance
        {
            get
            {
                if (m_instance == null)
                    m_instance = new Log();
                return m_instance;
            }
        }
    }

请不要纠结用单件实例化,在这里只是单件适合一点,其实一个Log对象是可以做为容器上下文的,比如在用Around通知时,可以在Before方法里对Log对象的一个成员(上面没给出)赋值,After方法可以使用。

如何调用呢?直接调用一定是这样的:

单从Advice(通知)实现AOP

现在就要去实现组件的第二个也是最后一个类了,AspectInvoker类,AspectInvoker类很大,1700多行, 但也很简单,只有6个方法,每个方法有34个重载,就不贴全码了,最后给两个类的下载。

先来个简单的调用一睹为快:

单从Advice(通知)实现AOP

是不是已经有点意思了呢?AspectInvoker.Beore有两个参数,第一个是AspectAdvice的子类,这里是个Log类的对象,第二个是一个Action委托,这里是myClass.Act方法。AspectInvoker.Beore方法是超级简单的:

        public static void Before(AspectAdvice advice, Action act)
        {
            advice.Before(act.Target, act.Method);
            act();
        }

怎么样?最复杂的思路已经完成了!执行上有效率问题吗?

再来个调用MyClass类的Fun方法的,这个方法有个参数,有个返回值:

单从Advice(通知)实现AOP

这个是Around的调用,第一个参数不变,第二个参数不变,因为myClass.Fun有参数,所以第三个是myClass.Fun的第一个参数(如果有多个,以次往后写就是了),最后一个输出参数是myClas.Fun的返回值。AspectInvoker.Around的实现只是比猫画虎罢了,前面说了,最复杂的思路已经完成!

        public static void Around<T, TResult>(AspectAdvice advice, Func<T, TResult> fun, T t, out TResult result)
        {
            advice.Before(fun.Target, fun.Method, t);
            result = fun(t);
            advice.After(fun.Target, fun.Method, t);
        }

再来个抛异常的,这个可以用AspectInvoker.AfterEnsure或AspectInvoker.Throw来测试,下图是AspectInvoker.AfterEnsure的结果:

单从Advice(通知)实现AOP

异常发生了,Log.After也执行了,这就是AspectInvoker.AfterEnsure,确保了After的执行。如果用AspectInvoker.Throw,只有异常时才执行Log.Throw,调用方法的实现还是画虎罢了:

        public static void AfterEnsure<T, TResult>(AspectAdvice advice, Func<T, TResult> fun, T t, out TResult result)
        {
            try
            {
                result = fun(t);
            }
            finally
            {
                advice.After(fun.Target, fun.Method, t);
            }
        }
        public static void Throw<T, TResult>(AspectAdvice advice, Func<T, TResult> fun, T t, out TResult result)
        {
            try
            {
                result = fun(t);
            }
            catch (Exception exp)
            {
                advice.Throw(fun.Target, fun.Method, t);
                throw exp;
            }
        }

最后还有MyClass.SFun的静态方法,这里就不多说了,说多了都是Repeat!

总结:

1,AspectInvoker类的6个调用方法都可以调用参数在17个以下的目标方法,因为泛型Action和泛型Func最多都是16个,所以我也写到了16个(这个完全不是限制)。

2,AspectInvoker类不能调用有out参数和ref参数的目标方法。

3,这个组件是简单的,是高效的,和现有的AOP框架肯定是有出入的!请不要和它们对比,这个组件的目的不在于此!

4,MyClass类不用包装,不用继承什么,AspectInvoker关注的只是方法,不管是静态方法还是实例方法,不管是你的方法还是我的方法....

5,你真的知道怎么用了吧?大多数人是没问题的,我担心的只是不爱思考的同学:

1),Log的实现现在是数据库,随时换成记录到文本没问题的

2),AspectAdvice的方法都有三个参数,第一个是目标方法的对象target(如果是静态方法,target是null),第二个是目标方法的MethodInfo,第三个是目标方法的参数数组

比如target类型就可以做些事情等等。

3)

6,下载

完...

2013-09-27修改:

把调用也写到AspectAdvice,调用方法有所改善,返回值不用out参数,而是直接返回,上述例子中新的调用如下:

        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();

            Log.Instance.BeforeAction(myClass.Act);

            int len = Log.Instance.AroundFunc(myClass.Fun, "a string param");
            Console.WriteLine(len);

            );
            Console.WriteLine(result);
        }

注:为什么要分开Action和Func的调用方法名称呢,因为Action<T,T>和Func<T,T,TResult>在C#里无法区分,方法签名不包括返回值,这点使人不是很爽!

AspectAdvice下载