摘要:面向对象的思想强调"一切皆是对象",在面向对象的程序中我们使用真实概念的模型思考问题,使得整个软件系统开发可以像搭建房屋一样有条不紊。然而面向对象也并非完美无缺的,它更注重于对象层次结构方面的东西,对于如何更好的管理对象行为内部结构,还存在着些许不足。那么我们如何使这个问题的得到更完美的解决呢?答案就是AOP。
主要内容:
- AOP简述
- 利用动态代理实现AOP
- 总结
一、AOP简述
AOP的概念早在上个世纪九十年代初就已经出现了,当时的研究人员通过对面向对象思想局限性的分析研究出了一种新的编程思想来帮助开发者减少代码重复提高开发效率,那就是AOP,Aspect-Oriented Programming。AOP是OOP的补充,是GOF的延续。我们知道设计模式是对于面向对象设计中经验的总结,它孜孜不断追求的就是调用者与被调用者之间的解耦。有了设计模式我们可以更有效的利用面向对象的特性,使得整个软件设计更加灵活、优雅。但是设计模式是基于面向对象的思想而形成的,更多的时候关注的是对象层次的东西,在解决对象行为内部问题方面却有些不足。AOP的出现恰恰就是对面向对象思想做出了完美的补充。
上图显示了软件的纵向和横向结构,从纵向结构来看就是我们软件系统的各个模块,它主要负责处理我们的核心业务(例如商品订购、购物车查看);而从横向结构来看,我们几乎每个系统又包含一些公共模块(例如权限、日志模块等)。这些公共模块分布于我们各个核心业务之中(例如订购和查看商品明细的过程都需要检查用户权限、记录系统日志等)。这样一来不仅在开发过程中要处处关注公共模块的处理而且开发后维护起来也是十分麻烦。而有了AOP之后将应用程序中的商业逻辑同对其提供支持的通用服务进行分离,使得开发人员可以更多的关注核心业务开发。
二、利用动态实现AOP
上面我们说了一些关于AOP所要解决的问题以及这样做的好处,下面我们主要看一下如何实现AOP。
AOP的实现一般分为:动态代理(DynamicProxy)和静态织入(StaticWeave)两种方式。这里我们主要说的是如何利用Emit来自己实现动态代理方式的AOP(关于Emit的知识大家可以看一下我的另一篇博客反射发出--Emit ),以此来帮助大家更深入的理解AOP。
在开始动态代理之前假设我们有这样一个类,它是负责处理我们的业务逻辑的。
先看对应接口
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace DanymicProxy
- {
- interface IBusinessLogic
- {
- void ShowMessage(string msg);
- int Calculate();
- }
- }
相应的类
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace DanymicProxy
- {
- 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内部都添加上记录日志的代码 。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace DanymicProxy
- {
- 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);
- }
- }
然后编写一个操作日志的拦截器
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace DanymicProxy
- {
- 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", new object[] { 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<T> 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, new object[]{ 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<MyBusinessLogic>.CreateProxy(new LogInterceptor()).ShowMessage("Hello World!");
- Proxy<MyBusinessLogic>.CreateProxy(new LogInterceptor()).Calculate();
- }
- }
- }
运行效果
三、总结:
上面简单的介绍了如何用动态代理的方式实现AOP,主要是帮助大家理解动态代理AOP的大致思路。在实际开发中我们可能更多时候会选择一些AOP的工具(例如Castle中的Aspect#、Spring AOP、AspectDNG等),这些内容(包括静态织入方式实现AOP)我们今后有机会再一块学习。