静态代理和动态代理方式分别实现AOP拦截功能


摘要:面向对象的思想强调"一切皆是对象",在面向对象的程序中我们使用真实概念的模型思考问题,使得整个软件系统开发可以像搭建房屋一样有条不紊。然而面向对象也并非完美无缺的,它更注重于对象层次结构方面的东西,对于如何更好的管理对象行为内部结构,还存在着些许不足。那么我们如何使这个问题的得到更完美的解决呢?答案就是AOP。

主要内容:

  1. AOP简述
  2. 利用动态代理实现AOP
  3. 总结

一、AOP简述

上面我们说了一些关于AOP所要解决的问题以及这样做的好处,下面我们主要看一下如何实现AOP。

AOP的实现一般分为:动态代理(DynamicProxy)和静态织入(StaticWeave)两种方式。这里我们主要说的是如何利用Emit来自己实现动态代理方式的AOP(关于Emit的知识大家可以看一下我的另一篇博客反射发出--Emit),以此来帮助大家更深入的理解AOP。

在开始动态代理之前假设我们有这样一个类,它是负责处理我们的业务逻辑的。

先看对应接口和相应的类

    interface IBusinessLogic
    {
        void ShowMessage(string msg);
        int Calculate();
    }
    

    public class MyBusinessLogic : IBusinessLogic
    {
        public virtual void ShowMessage(string msg)
        {
            Console.WriteLine(msg);
        }

        public virtual int Calculate()
        {
            int sum = 0;
            for (int i = 1; i <= 100; ++i)
            {
                sum += +i;
            }
            Console.WriteLine(sum);
            return sum;
        }
    }

现在我们需要给所有的核心业务添加日志记录功能,此时大概会有下面几种选择:

1.在ShowMessage和Calculate内部都添加上记录日志的代码 。

    public class MyBusinessLogic : IBusinessLogic
    {
        public virtual void ShowMessage(string msg)
        {
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.MyBusinessLogic ShowMessage is called!");
            Console.WriteLine(msg);
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.MyBusinessLogic ShowMessage has finished!");
        }

        public virtual int Calculate()
        {
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.Calculate ShowMessage is called!");
            int sum = 0;
            for (int i = 1; i <= 100; ++i)
            {
                sum += +i;
            }
            Console.WriteLine(sum);
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.Calculate ShowMessage has finished!");
            return sum;
        }
    }

2.重新写一个类继承于MyBusinessLogic并对其中的方法重写。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DanymicProxy
{
    class MyBusinessLogicDecoration : MyBusinessLogic
    {
        public override void ShowMessage(string msg)
        {
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.MyBusinessLogic ShowMessage is called!");
            base.ShowMessage(msg);
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.MyBusinessLogic ShowMessage has finished!");
        }

        public override int Calculate()
        {
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.Calculate ShowMessage is called!");
            int t = base.Calculate();
            Console.WriteLine(DateTime.Now.ToString() + ":DanymicProxy.Calculate ShowMessage has finished!");
            return t;

        }

    }
}

3.写一个静态代理类。

首先看一下代理中用到的拦截器接口

然后编写一个操作日志的拦截器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DanymicProxy
{
    public interface IInterceptor
    {
        object Invoke(object obj, string methodName, object[] parameters);
    }


    public class LogInterceptor : IInterceptor
    {

        public object Invoke(object obj, string methodName, object[] parameters)
        {
            Console.WriteLine(DateTime.Now.ToString() + ":" + obj.ToString() + "'s " + methodName + " is called!");
            object rst = obj.GetType().GetMethod(methodName).Invoke(obj, parameters);
            Console.WriteLine(DateTime.Now.ToString() + ":" + obj.ToString() + "'s " + methodName + " has finished!");
            return rst;
        }
    }
}

再看静态代理类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DanymicProxy;

namespace DanymicProxy
{
    class MyBusinessLogicProxy
    {
        private IInterceptor _interceptor = null;
        public MyBusinessLogicProxy(IInterceptor interceptor)
        {
            _interceptor = interceptor;
        }
        public virtual void ShowMessage(string msg)
        {
            _interceptor.Invoke(new MyBusinessLogic(), "ShowMessage", newobject[] { msg });
        }

        public virtual int Calculate()
        {
            return Convert.ToInt32(_interceptor.Invoke(new MyBusinessLogic(), "Calculate", null));
        }
    }
}

接着我们可以使用下面的代码做测试

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DanymicProxy
{
    class Program
    {
        static void Main(string[] args)
        {
            (new MyBusinessLogicProxy(new LogInterceptor())).ShowMessage("Hello World!");
            (new MyBusinessLogicProxy(new LogInterceptor())).Calculate(); ;
        }
    }
}

4.使用动态代理

相对于前两种方法,第三种方法应该算是比较好的方法了,可是做起来比较麻烦。不仅必须给每个业务类都重新写一个包含日志处理的代理类,而且如果我有其他类似日志处理的模块(例如权限模块)需要添加还要再写包含其他功能的代理类。这样做起来不仅工作量大,而且再维护起来也十分麻烦。怎么办?方法就是使用动态代理。

我们下面要实现的动态代理,其运行机制和上面说的静态代理可以说是完全相同的(事实上我们编写Emit的时候就是仿照静态代理类来写的),只不过重复写代理类的工作交给程序来做了而已。

具体过程:首先根据泛型类型创建名字为"泛型+Proxy"的类;在类中声明一个IInterceptor型的私有变量_interceptor赋值为null;接着创建构造函数,其参数为IInterceptor类型;然后我们遍历泛型中的方法,创建名称、返回类型、参数均与泛型方法一致的方法,然后在方法中使用interceptor的Invoke方法调用对应的方法(参数分别为泛型对象、方法名和参数);最后实例化此类,并将其返回。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace DanymicProxy
{
    public class Proxy where T : class
    {
        public static T CreateProxy(IInterceptor interceptor)
        {
            //构建程序集
            AssemblyName aName = new AssemblyName("Cmj.DotNet");
            AppDomain aDomain = AppDomain.CurrentDomain;//应用程序域,这里设为当前域
            AssemblyBuilder aBuidler = aDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave);
            //定义模块
            ModuleBuilder mBuidler = aBuidler.DefineDynamicModule("MyModule", "Cmj.DotNet.dll");
            //创建类型(其实就是一个类)
            StringBuilder sbClassName = new StringBuilder("Cmj.DotNet.");
            sbClassName.Append(typeof(T).Name);
            sbClassName.Append("Proxy");
            TypeBuilder tBuidler = mBuidler.DefineType(sbClassName.ToString(), TypeAttributes.Public | TypeAttributes.Class, typeof(T));//继承于T

            //--实现类--//
            //定义私有字段
            FieldBuilder fbInterceptor = tBuidler.DefineField("_interceptor", typeof(IInterceptor), FieldAttributes.Private);
            //为私有变量赋值
            fbInterceptor.SetConstant(null);
            //定义构造函数
            ConstructorBuilder ctstBuilder = tBuidler.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(IInterceptor) });
            ILGenerator ctstGenerator = ctstBuilder.GetILGenerator();
            ctstGenerator.Emit(OpCodes.Ldarg_0);
            ctstGenerator.Emit(OpCodes.Ldarg_1);
            ctstGenerator.Emit(OpCodes.Stfld, fbInterceptor);//给字段赋值
            ctstGenerator.Emit(OpCodes.Ret);
            //定义方法
            MethodInfo[] methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public);
            Type retType = null;
            Type[] paramsType = null;
            ParameterInfo[] parameters = null;
            MethodBuilder mtdBuidler = null;
            ILGenerator mtdGenerator = null;
            foreach (MethodInfo method in methods)
            {
                if (method.Name != "ToString" && method.Name != "Equals" && method.Name != "GetHashCode" && method.Name != "GetType")
                {
                    //获得返回值类型
                    retType = method.ReturnType;
                    //获得参数类型
                    parameters = method.GetParameters();
                    paramsType = new Type[parameters.Length];
                    for (int i = 0; i < parameters.Length; ++i)
                    {
                        paramsType[i] = parameters[i].ParameterType;
                    }
                    //实现方法体
                    mtdBuidler = tBuidler.DefineMethod(method.Name, MethodAttributes.Public | MethodAttributes.Virtual, CallingConventions.Standard, retType, paramsType);
                    mtdGenerator = mtdBuidler.GetILGenerator();
                    mtdGenerator.Emit(OpCodes.Ldarg_0);
                    mtdGenerator.Emit(OpCodes.Ldfld, fbInterceptor);
                    mtdGenerator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(new Type[0]));
                    mtdGenerator.Emit(OpCodes.Ldstr, method.Name);
                    if (paramsType.Length == 0)
                    {
                        mtdGenerator.Emit(OpCodes.Ldnull);
                    }
                    else
                    {
                        LocalBuilder paras = mtdGenerator.DeclareLocal(typeof(object[]));
                        mtdGenerator.Emit(OpCodes.Ldc_I4, paramsType.Length);
                        mtdGenerator.Emit(OpCodes.Newarr, typeof(object));
                        mtdGenerator.Emit(OpCodes.Stloc, paras);

                        for (var j = 0; j < paramsType.Length; j++)
                        {
                            mtdGenerator.Emit(OpCodes.Ldloc, paras);
                            mtdGenerator.Emit(OpCodes.Ldc_I4, j);
                            mtdGenerator.Emit(OpCodes.Ldarg, j + 1);

                            mtdGenerator.Emit(OpCodes.Stelem_Ref);
                        }
                        mtdGenerator.Emit(OpCodes.Ldloc, paras);
                    }
                    mtdGenerator.Emit(OpCodes.Callvirt, typeof(IInterceptor).GetMethod("Invoke"));
                    if (retType == typeof(void))
                    {
                        mtdGenerator.Emit(OpCodes.Pop);
                    }
                    mtdGenerator.Emit(OpCodes.Ret);
                }
            }

            //创建引用、调用方法
            Type proxyType = tBuidler.CreateType();
            aBuidler.Save("Cmj.DotNet.dll");
            object proxy = Activator.CreateInstance(proxyType, newobject[]{ interceptor});//实例化
            return proxy as T;
        }
    }
}

最后我们测试一下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DanymicProxy
{
    class Program
    {
        static void Main(string[] args)
        {
            Proxy.CreateProxy(new LogInterceptor()).ShowMessage("Hello World!");
            Proxy.CreateProxy(new LogInterceptor()).Calculate();
        }
    }
}

运行效果

三、总结:

上面简单的介绍了如何用动态代理的方式实现AOP,主要是帮助大家理解动态代理AOP的大致思路。在实际开发中我们可能更多时候会选择一些AOP的工具(例如Castle中的Aspect#、Spring AOP、AspectDNG等),这些内容(包括静态织入方式实现AOP)我们今后有机会再一块学习。


作品采用知识共享署名 2.5 中国大陆许可协议进行许可,欢迎转载,演绎或用于商业目的。但转载请注明来自崔江涛(KenshinCui),并包含相关链接。

出处:https://www.cnblogs.com/kenshincui/archive/2011/01/01/1923954.html

Ioc