前言 AOP,大家都是聽過的,它是一種面向切麵的設計模式。 不過AOP雖然是被稱為設計模式,但我們應該很少能看到AOP設計的框架。為什麼呢? 因為,AOP單獨設計的框架幾乎是無法使用的。普遍的情況是,AOP要是和其他設計模式結合在一起使用。 所以,AOP雖然是設計模式,但我認為它更接近一種設計元素, ...
前言
AOP,大家都是聽過的,它是一種面向切麵的設計模式。
不過AOP雖然是被稱為設計模式,但我們應該很少能看到AOP設計的框架。為什麼呢?
因為,AOP單獨設計的框架幾乎是無法使用的。普遍的情況是,AOP要是和其他設計模式結合在一起使用。
所以,AOP雖然是設計模式,但我認為它更接近一種設計元素,是我們在設計框架的作料。
其實AOP的原理就是將公共的部分提取出來,這件事,即便不考慮設計模式,每個開發人員在工作時也是會做的。也就是說,在AOP設計模式被提出來之前,我們就在應用AOP的設計了。
那麼,為什麼還要單獨將AOP拿出來說事呢?
我認為,主要目的應該是要強化切麵的重要性。因為設計框架時加入AOP的理念,確實會讓框架更加立體。
AOP的應用
AOP既然是一種作料,那麼它的應用就是多種多樣的;它可以出現在任何場合的。
下麵我們舉出一個例子,來說明AOP的應用。
----------------------------------------------------------------------------------------------------
我們在開發的時候,通常會有這樣的需求。
[將函數的入參和返回值記錄到日誌中][入參中為負數拋出異常]
當我們面對這樣的需求時,通常會將入參和返回值全部傳到一個獨立的操作函數中,對其進行相應的操作。
這樣實現,就是AOP的理念;不過開發者處理時,稍微繁瑣了一點,因為每個函數都要處理。
為了減少這種重覆操作,讓我們一起來編寫函數的切麵AOP吧。
AOP框架的實現
首先,我們一起看下AOP框架應用後的效果。
在下麵代碼中,可以看到,我們定義了一個AOPTest類,然後調用了他的Test方法,之後傳入了一個正數和一個負數,如果函數拋出異常,我們將輸出異常的消息。
class Program { static void Main(string[] args) { AOPTest test = new AOPTest(); try { test.Test(518); test.Test(-100); } catch(Exception ex) { Console.WriteLine(ex.Message); } Console.ReadLine(); } }
接下來我們看下AOPTest類的定義。
[Kiba] public class AOPTest : ContextBoundObject { public string Test(int para) { Console.WriteLine(para); return "數字為:" + para; } }
代碼如上所示,很簡單,就是輸出了入參,不過有兩個地方需要註意,該類繼承了ContextBoundObject類,並且擁有一個KIba的特性。
然後,我們看下運行結果。
從運行結果中我們看到,第一個函數正常輸出,但第二個函數拋出了異常,而且異常的Message是異常兩個漢字。
這就是我們AOP實行的效果了,我們的AOP框架對函數入參進行了判斷,如果是正數,就正常運行,如果為負數就拋出異常。
下麵我們一起來看看AOP框架是如何實現這樣的效果的。
首先我們一起來看下Kiba這個特性。
[AttributeUsage(AttributeTargets.Class)] public class KibaAttribute : ContextAttribute { public KibaAttribute() : base("Kiba") { } public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg) { ctorMsg.ContextProperties.Add(new KibaContextProperty()); } }
代碼如上所示,很簡單很基礎的一個特性,不過它繼承了ContextAttribute類,並重寫了其下的方法GetPropertiesForNewContext。
這個方法是乾什麼的呢?
我們可以從函數名的直譯來理解它是乾什麼的,GetPropertiesForNewContext直譯過來就是創建新對象時獲取他的屬性。然後我們看到,我們重新了該方法後又為他添加了一個新的屬性。
而我們添加的這個新的屬性將截獲擁有該特性的類的函數。
【PS:該描述並不是ContextAttribute真實的運行邏輯,不過,初學時,我們可以先這樣理解,當我們更深入的理解了函數的運行機制後,自然就明白該類的意義。】
下麵我們看下KibaContextProperty類。
public class KibaContextProperty : IContextProperty, IContributeObjectSink { public KibaContextProperty() { } public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next) { return new KibaMessageSink(next); } public bool IsNewContextOK(Context newCtx) { return true; } public void Freeze(Context newCtx) { } public string Name { get { return "Kiba"; } } }
代碼如上所示,依然很簡單,只是繼承並實現了IContextProperty和IContributeObjectSink兩個介面。
其中我們重點看下GetObjectSink方法,該方法用於截獲函數。
我們可以看到該方法的兩個參數,但我們只用到了一個IMessageSink ,並且,該方法的返回值也是IMessageSink。
所以,我們可以想到,該方法的本來面目是這樣的。
public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next) { return next; }
也就是說,IMessageSink 封裝了函數的一切內容,那麼我們的AOP實現的地方也就找到了。
於是我們用KibaMessageSink類處理一下IMessageSink 。
KibaMessageSink代碼如下:
public class KibaMessageSink : IMessageSink { private KAspec kaspec = new KAspec(); private IMessageSink nextSink; public KibaMessageSink(IMessageSink next) { nextSink = next; } public IMessageSink NextSink { get { return nextSink; } } public IMessage SyncProcessMessage(IMessage msg) { IMethodCallMessage call = msg as IMethodCallMessage; if (call != null) { //攔截消息,做前處理 kaspec.PreExcute(call.MethodName, call.InArgs); } for (int i = 0; i < call.InArgs.Count(); i++) { var para = call.InArgs[i]; var type = para.GetType(); string typename = type.ToString().Replace("System.Nullable`1[", "").Replace("]", "").Replace("System.", "").ToLower(); if (typename == "int32") { int inparame = Convert.ToInt16(call.InArgs[i]); if (inparame < 0) { throw new Exception("異常"); } } } //傳遞消息給下一個接收器 IMessage retMsg = nextSink.SyncProcessMessage(call as IMessage); IMethodReturnMessage dispose = retMsg as IMethodReturnMessage; if (dispose != null) { //調用返回時進行攔截,併進行後處理 kaspec.EndExcute(dispose.MethodName, dispose.OutArgs, dispose.ReturnValue, dispose.Exception); } return retMsg; } public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) { return null; } }
我們重點看下SyncProcessMessage方法。
可以看到,我們在方法調用先調用了KAspec類的PreExcute方法,該方法用於把入參輸出到日誌中。
接下來,我們對入參進行了判斷,如果入參是負數,我們將不執行函數,直接拋出異常。
然後我們調用KAspec類的EndExcute方法,將返回值輸出到日誌中。
再然後,我們才返回IMessage,讓函數完結。
下麵我們一起看下KAspec類的實現。
/// <summary> /// 切麵 /// </summary> public class KAspec { #region 處理 /// <summary> /// 前處理 /// </summary> public void PreExcute(string MethodName, object[] InParams) { Logger.Info("==================== " + MethodName + ":" + " Start===================="); Logger.Info(string.Format("參數數量:{0}", InParams.Count())); for (int i = 0; i < InParams.Count(); i++) { Logger.Info(string.Format("參數序號[{0}] ============ 參數類型:{1} 執行類:{1}", i + 1, InParams[i])); Logger.Info("傳入參數:"); string paramXMLstr = XMLSerializerToString(InParams[i], Encoding.UTF8); Logger.Info(paramXMLstr); } } /// <summary> /// 後處理 /// </summary> public void EndExcute(string MethodName, object[] OutParams, object ReturnValue, Exception ex) { Type myType = ReturnValue.GetType(); Logger.Info(string.Format("返回值類型:{0}", myType.Name)); Logger.Info("返回值:"); if (myType.Name != "Void") { string resXMLstr = DataContractSerializerToString(ReturnValue, Encoding.UTF8); Logger.Info(resXMLstr); } if (OutParams.Count() > 0)//out 返回參數 { Logger.Info(string.Format("out返回參數數量:{0}", OutParams.Count())); for (int i = 0; i < OutParams.Count(); i++) { Logger.Info(string.Format("參數序號[{0}] == 參數值:{1}", i + 1, OutParams[i])); } } if (ex != null) { Logger.Error(ex); } Logger.Info("==================== " + MethodName + ":" + " End===================="); } }
代碼如上所示,就是簡單的日誌輸出。
到此,我們的AOP框架就編寫完成了;其上的代碼編寫都是為KAspec服務,因為KAspec才是切麵。
也就是說,只要將特性Kiba賦予給類,那麼該類的函數,就被攔截監聽,然後我們就可以KAspec切麵中,做我們想做的操作了。
最後,我們再回頭看下AOPTest類。
[Kiba] public class AOPTest : ContextBoundObject
可以看到,該類不止擁有Kiba特性,還繼承了ContextBoundObject類,該類是乾什麼的呢?
ContextBoundObject類是內容邊界對象,只有繼承了ContextBoundObject類的類,其類中才會駐留的Context上下文,並且會被ContextAttribute特性攔截監聽。
呃,其實,這樣解釋還是有點不太正確,不過我也沒找到更好的說明方式,如果你還理解不了,也可以去MSDN查詢下,當然,MSDN的解釋是反人類的,需要做好心理準備。
----------------------------------------------------------------------------------------------------
框架代碼已經傳到Github上了,歡迎大家下載。
Github地址:https://github.com/kiba518/KAOP
----------------------------------------------------------------------------------------------------
註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接!
若您覺得這篇文章還不錯,請點擊下右下角的【推薦】,非常感謝!