動態代理方式實現AOP

摘要:面向對象的思想強調"一切皆是對象",在面向對象的程序中我們使用真實概念的模型思考問題,使得整個軟件系統開發可以像搭建房屋一樣有條不紊。然而面向對象也並非完美無缺的,它更注重於對象層次結構方面的東西,對於如何更好的管理對象行爲內部結構,還存在着些許不足。那麼我們如何使這個問題的得到更完美的解決呢?答案就是AOP。

主要內容:

  1. AOP簡述
  2. 利用動態代理實現AOP
  3. 總結

一、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)我們今後有機會再一塊學習。

來自崔江濤(KenshinCui)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章