AOP技术研究 再续
第四部分? .Net平臺AOP技術(shù)研究
4.1.Net平臺AOP技術(shù)概覽
.Net平臺與Java平臺相比,由于它至今在服務(wù)端仍不具備與unix系統(tǒng)的兼容性,也不具備類似于Java平臺下J2EE這樣的企業(yè)級容器,使 得.Net平臺在大型的企業(yè)級應(yīng)用上,常常為人所詬病。就目前而言,.Net平臺并沒有提供AOP技術(shù)的直接實(shí)現(xiàn),而微軟在未來對于.Net的發(fā)展戰(zhàn)略目 標(biāo),我們?nèi)晕纯芍5蚁嘈盼④泴τ谀壳爸耸挚蔁岬腁OP技術(shù)應(yīng)該不會視而不見。也許在未來的.Net平臺下,會出現(xiàn)類似于Spring那樣的輕量級 IoC容器,加上O/R Mapping的進(jìn)一步實(shí)現(xiàn)與完善,隨著Windows Server操作系統(tǒng)的逐步推新,.Net平臺對于企業(yè)級系統(tǒng)開發(fā)的支持會越來越多。
AOP技術(shù)在.Net平臺中的應(yīng)用,相較于Java平臺而言,還遠(yuǎn)不夠成熟,功能也相對較弱,目前能夠投入商用的AOP工具幾乎沒有。借鑒Java 開源社區(qū)的成功,.Net平臺下AOP工具的開發(fā)也都依托于開源社區(qū)的力量。眾多開源愛好者,仍然在堅(jiān)持不懈對AOP技術(shù)進(jìn)行研究和實(shí)踐,試圖找到AOP 技術(shù)與.Net之間的完美結(jié)合點(diǎn),從而開發(fā)出真正能夠商用的功能強(qiáng)大的AOP工具。就目前而言,大部分在.Net平臺下的AOP工具,大部分均脫胎于 Java平臺下的AOP工具,例如Spring.Net之于Spring,Eos之于AspectJ。由于Java平臺和.Net平臺在語言機(jī)制上的相似 性,使得它們在實(shí)現(xiàn)AOP的技術(shù)機(jī)制上,大體相似,無非是利用靜態(tài)織入或動態(tài)織入的方式,完成對aspect的實(shí)現(xiàn)。
目前在.Net平臺下的AOP大部分仍然處于最初的開發(fā)階段,各自發(fā)布的版本基本都是beta版。其中較有代表性的AOP工具包括Aspect#,Spring.Net,Eos等。
Aspect#是基于Castle動態(tài)代理技術(shù)實(shí)現(xiàn)的。Castle動態(tài)代理技術(shù)利用了.Net的Emit技術(shù),生成一個新的類去實(shí)現(xiàn)特定的接口, 或者擴(kuò)展一個已有的類,并將其委托指向IInterceptor接口的實(shí)現(xiàn)類。通過Castle動態(tài)代理技術(shù),就可以攔截方法的調(diào)用,并將Aspect的 業(yè)務(wù)邏輯織入到方法中。利用Castle動態(tài)代理技術(shù),最大的缺陷是它只對虛方法有效,這限制了Aspect#的一部分應(yīng)用。
Spring.Net從根本意義上來說,是對Spring工具從Java平臺向.Net平臺的完全移植。它在AOP的實(shí)現(xiàn)上與Spring幾乎完全相似,仍然利用了AOP聯(lián)盟提供的***、Advice等實(shí)現(xiàn)AOP。Spring.Net的配置文件也與Spring相同。
Eos采用的是靜態(tài)織入的技術(shù)。它提供了獨(dú)有的編譯器,同時還擴(kuò)展了C#語法,以類似于AspectJ的結(jié)構(gòu),規(guī)定了一套完整的AOP語法,諸如 aspect,advice,before,after,pointcut等。Eos充分的利用了.Net中元數(shù)據(jù)的特點(diǎn),以IL級的代碼對方面進(jìn)行織 入,這也使得它的性能與其他AOP工具比較有較大的提高。
4.2 .Net平臺下實(shí)現(xiàn)AOP的技術(shù)基礎(chǔ)
如前所述,在.Net平臺下實(shí)現(xiàn)AOP,采用的方式主要是靜態(tài)織入和動態(tài)織入的方式。在本文中,我將充分利用.Net的技術(shù)特性,包括元數(shù)據(jù)、 Attribute、.Net Remoting的代理技術(shù),將其綜合運(yùn)用,最終以動態(tài)織入的方式實(shí)現(xiàn)AOP公共類庫。本節(jié)將介紹實(shí)現(xiàn)AOP所必需的.Net知識。
4.2.1元數(shù)據(jù)(metadata)
4.2.1.1元數(shù)據(jù)概述
元數(shù)據(jù)是一種二進(jìn)制信息,用以對存儲在公共語言運(yùn)行庫(CLR)中可移植可執(zhí)行文件 (PE) 或存儲在內(nèi)存中的程序進(jìn)行描述。在.Net中,如果將代碼編譯為 PE 文件時,便會將元數(shù)據(jù)插入到該文件的一部分中,而該代碼被編譯成的Microsoft 中間語言 (MSIL),則被插入到該文件的另一部分中。在模塊或程序集中定義和引用的每個類型和成員都將在元數(shù)據(jù)中進(jìn)行說明。執(zhí)行代碼時,運(yùn)行庫將元數(shù)據(jù)加載到內(nèi) 存中,并引用它來發(fā)現(xiàn)有關(guān)代碼的類、成員、繼承等信息。
元數(shù)據(jù)以非特定語言的方式描述在代碼中定義的每一類型和成員。它存儲的信息包括程序集的信息,如程序集的版本、名稱、區(qū)域性和公鑰,以及該程序集所 依賴的其他程序集;此外,它還包括類型的說明,包括類型的基類和實(shí)現(xiàn)的接口,類型成員(方法、字段、屬性、事件、嵌套的類型)。
在.Net Framework中,元數(shù)據(jù)是關(guān)鍵,該模型不再需要接口定義語言 (IDL) 文件、頭文件或任何外部組件引用方法。元數(shù)據(jù)允許 .NET 語言自動以非特定語言的方式對其自身進(jìn)行描述,此外,通過使用Attribute,可以對元數(shù)據(jù)進(jìn)行擴(kuò)展。元數(shù)據(jù)具有以下主要優(yōu)點(diǎn):
1. 自描述文件
公共語言運(yùn)行庫(CLR)模塊和程序集是自描述的。模塊的元數(shù)據(jù)包含與另一個模塊進(jìn)行交互所需的全部信息。元數(shù)據(jù)自動提供COM中IDL的功能,允 許將一個文件同時用于定義和實(shí)現(xiàn)。運(yùn)行庫模塊和程序集甚至不需要向操作系統(tǒng)注冊。運(yùn)行庫使用的說明始終反映編譯文件中的實(shí)際代碼,從而提高應(yīng)用程序的可靠 性。
2.語言互用性和更簡單的基于組件的設(shè)計
元數(shù)據(jù)提供所有必需的有關(guān)已編譯代碼的信息,以供您從用不同語言編寫的 PE 文件中繼承類。您可以創(chuàng)建用任何托管語言(任何面向公共語言運(yùn)行庫的語言)編寫的任何類的實(shí)例,而不用擔(dān)心顯式封送處理或使用自定義的互用代碼。
3.Attribute
.NET Framework允許在編譯文件中聲明特定種類的元數(shù)據(jù)(稱為Attribute)。在整個 .NET Framework 中到處都可以發(fā)現(xiàn)Attribute的存在,Attribute用于更精確地控制運(yùn)行時程序如何工作。另外,用戶可以通過自定義屬性向 .NET Framework 文件發(fā)出用戶自己的自定義元數(shù)據(jù)。
4.2.1.2元數(shù)據(jù)的結(jié)構(gòu)
在PE文件中與元數(shù)據(jù)有關(guān)的主要包括兩部分。一部分是元數(shù)據(jù),它包含一系列的表和堆數(shù)據(jù)結(jié)構(gòu)。每個元數(shù)據(jù)表都保留有關(guān)程序元素的信息。例如,一個元 數(shù)據(jù)表說明代碼中的類,另一個元數(shù)據(jù)表說明字段等。如果您的代碼中有10個類,類表將有10行,每行為1個類。元數(shù)據(jù)表引用其他的表和堆。例如,類的元數(shù) 據(jù)表引用方法表。元數(shù)據(jù)以四種堆結(jié)構(gòu)存儲信息:字符串、Blob、用戶字符串和 GUID。所有用于對類型和成員進(jìn)行命名的字符串都存儲在字符串堆中。例如,方法表不直接存儲特定方法的名稱,而是指向存儲在字符串堆中的方法的名稱。
另一部分是MSIL指令,許多MSIL指令都帶有元數(shù)據(jù)標(biāo)記。元數(shù)據(jù)標(biāo)記在 PE 文件的 MSIL 部分中唯一確定每個元數(shù)據(jù)表的每一行。元數(shù)據(jù)標(biāo)記在概念上和指針相似,永久駐留在MSIL中,引用特定的元數(shù)據(jù)表。元數(shù)據(jù)標(biāo)記是一個四個字節(jié)的數(shù)字。最高 位字節(jié)表示特定標(biāo)記(方法、類型等)引用的元數(shù)據(jù)表。剩下的三個字節(jié)指定與所說明的編程元素對應(yīng)的元數(shù)據(jù)表中的行。如果用C#定義一個方法并將其編譯到 PE文件中,下面的元數(shù)據(jù)標(biāo)記可能存在于PE文件的MSIL部分:
0x06000004
最高位字節(jié) (0x06) 表示這是一個MethodDef標(biāo)記。低位的三個字節(jié) (000004) 指示公共語言運(yùn)行庫在 MethodDef 表的第四行查找對該方法定義進(jìn)行描述的信息。
表4.1 描述了PE文件中元數(shù)據(jù)的結(jié)構(gòu)及其每部分的內(nèi)容:
| PE部分 ? ? ? | PE部分的內(nèi)容 ? ? ? |
表4.1? PE文件中的元數(shù)據(jù)
4.2.1.3元數(shù)據(jù)在運(yùn)行時的作用
由于在MSIL指令中包含了元數(shù)據(jù)標(biāo)記,因此,當(dāng)公共語言運(yùn)行庫(CLR)將代碼加載到內(nèi)存時,將向元 數(shù)據(jù)咨詢該代碼模塊中包含的信息。運(yùn)行庫對Microsoft 中間語言 (MSIL) 流執(zhí)行廣泛的分析,將其轉(zhuǎn)換為快速本機(jī)指令。運(yùn)行庫根據(jù)需要使用實(shí)時 (JIT) 編譯器將 MSIL 指令轉(zhuǎn)換為本機(jī)代碼,每次轉(zhuǎn)換一個方法。例如,有一個類APP,其中包含了Main()方法和Add()方法:
{
?? public static int Main()
?? {
????? int ValueOne = 10;
????? int ValueTwo = 20;??????
????? Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));
????? return 0;
?? }
?? public static int Add(int One, int Two)
?? {
????? return (One + Two);
?? }
}
通過運(yùn)行庫,這段代碼被加載到內(nèi)存中,并被轉(zhuǎn)化為MSIL:
.entrypoint
.maxstack? 3
.locals ([0] int32 ValueOne,
???????? [1] int32 ValueTwo,
???????? [2] int32 V_2,
???????? [3] int32 V_3)
IL_0000:? ldc.i4.s?? 10
IL_0002:? stloc.0
IL_0003:? ldc.i4.s?? 20
IL_0005:? stloc.1
IL_0006:? ldstr????? "The Value is: {0}"
IL_000b:? ldloc.0
IL_000c:? ldloc.1
IL_000d:? call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */
JIT 編譯器讀取整個方法的 MSIL,對其進(jìn)行徹底地分析,然后為該方法生成有效的本機(jī)指令。在 IL_000d 遇到 Add 方法 (/* 06000003 */) 的元數(shù)據(jù)標(biāo)記,運(yùn)行庫使用該標(biāo)記參考 MethodDef 表的第三行。
表4.2顯示了說明 Add 方法的元數(shù)據(jù)標(biāo)記所引用的 MethodDef 表的一部分:
| 行 ? ? ?? | 相對虛擬地址 (RVA) ? ? ? | ImplFlags ? ? ? | Flags ? ? ? | Name ? ? ?(指向字符串堆) ? ? | Signature ? ? ?(指向 Blob 堆) ? ? |
表4.2 元數(shù)據(jù)標(biāo)記
該表的每一列都包含有關(guān)代碼的重要信息。RVA 列允許運(yùn)行庫計算定義該方法的 MSIL 的起始內(nèi)存地址。ImplFlags 和 Flags 列包含說明該方法的位屏蔽(例如,該方法是公共的還是私有的)。Name 列對來自字符串堆的方法的名稱進(jìn)行了索引。Signature 列對在 Blob 堆中的方法簽名的定義進(jìn)行了索引。
通過利用元數(shù)據(jù),我們就可以獲得類的相關(guān)信息。如上所述,在類APP的MethodDef表中,可以獲得類APP的三個方法,以及方法的Flags 和方法簽名。而在.Net中,則提供了反射技術(shù),來支持這種對元數(shù)據(jù)信息的獲取。可以說,正是因?yàn)橛辛嗽獢?shù)據(jù),才使得AOP的攔截與織入功能的實(shí)現(xiàn)成為可 能。
4.2.2 Attribute
4.2.2.1 Attribute概述
通過對.Net元數(shù)據(jù)的分析,我們知道可以通過Attribute來擴(kuò)展元數(shù)據(jù)。那么什么是Attribute?在MSDN中,Attribute 被定義為“是被指定給某一聲明的一則附加的聲明性信息”。 我們可以通過Attribute來定義設(shè)計層面的信息以及運(yùn)行時(run-time)信息,也可以利用Attribute建立自描述(self- describing)組件。
Attribute可應(yīng)用于任何目標(biāo)元素,我們可以通過AttributeTargets枚舉指定其施加的目標(biāo),AttributeTargets枚舉在.Net中的定義如下:
C#語言: public enum AttributeTargets{
?? All=16383,
?? Assembly=1,
?? Module=2,
?? Class=4,
?? Struct=8,
?? Enum=16,
?? Constructor=32,
?? Method=64,
?? Property=128,
?? Field=256,
?? Event=512,
?? Interface=1024,
?? Parameter=2048,
?? Delegate=4096,
?? ReturnValue=8192
}
作為參數(shù)的AttributeTarges的值允許通過“或”操作來進(jìn)行多個值的組合,如果你沒有指定參數(shù),那么默認(rèn)參數(shù)就是All 。
不管是.Net Framework提供的Attribute,還是用戶自定義Attribute,都是通過[]施加到目標(biāo)元素上。雖然Attribute的用法與通常的 類型不一樣,但在.Net內(nèi)部,Attribute本質(zhì)上還是一個類。但是,Attribute類的實(shí)例化發(fā)生在編譯時,而非運(yùn)行時,因而達(dá)到了擴(kuò)展元數(shù) 據(jù)的目的。一個Attribute的多個實(shí)例可應(yīng)用于同一個目標(biāo)元素;并且Attribute可由從目標(biāo)元素派生的元素繼承。
4.2.2.2自定義Attribute
.Net Framework支持用戶自定義Attribute。自定義Attribute的方法與定義類一樣,唯一不同之處是自定義的Attribute必須繼承 Attribute類。Attribute類包含用于訪問和測試自定義Attribute的簡便方法。其中,Attribute類的構(gòu)造函數(shù)為 protected,只能被Attribute的派生類調(diào)用。Attribute類包含的方法主要為:
1.三個靜態(tài)方法
static Attribute GetCustomAttribute():這個方法有8種重載的版本,它被用來取出施加在類成員上指定類型的Attribute。
static Attribute[] GetCustomAttributes(): 這個方法有16種重載版本,用來取出施加在類成員上指定類型的Attribute數(shù)組。
static bool IsDefined():有八種重載版本,看是否指定類型的定制attribute被施加到類的成員上面。
2.兩個實(shí)例方法
bool IsDefaultAttribute(): 如果Attribute的值是默認(rèn)的值,那么返回true。
bool Match():表明這個Attribute實(shí)例是否等于一個指定的對象。
3.公共屬性
TypeId: 得到一個唯一的標(biāo)識,這個標(biāo)識被用來區(qū)分同一個Attribute的不同實(shí)例。
通過自定義Attribute,可使得用戶自定義的信息與Attribute施加的類本身相關(guān)聯(lián)。例如,給定一個自定義的 .NET 屬性,我們就可以輕松地將調(diào)用跟蹤Attribute與類的方法相關(guān)聯(lián):
C#語言: public class Bar{
??? [CallTracingAttribute("In Bar ctor")]
??? public Bar() {}
??? [CallTracingAttribute("In Bar.Calculate method")]
??? public int Calculate(int x, int y){ return x + y; }
}
請注意,方括號中包含 CallTracingAttribute 和訪問方法時輸出的字符串。這是將自定義元數(shù)據(jù)與 Bar 的兩個方法相關(guān)聯(lián)的Attribute語法。該自定義的Attribute實(shí)現(xiàn),如下所示:
C#語言:? using System;using System.Reflection;
[AttributeUsage( AttributeTargets.ClassMembers, AllowMultiple = false )]
public class CallTracingAttribute : Attribute
{???
??? private string m_TracingInfo;
??? public CallTracingAttribute(string info)
??? {
??????? m_TracingInfo = info;
??? }
??? public string TracingInfo
??? {
??????? get {return tracingInfo;}
??? }
}
通過自定義的CallTracingAttribute,將一段Tracing信息施加到類Bar的構(gòu)造函數(shù)和方法Calculate上。我們可以利用反射技術(shù)與Attribute類提供的方法,來獲得Bar類的元數(shù)據(jù)中包含的Attribute信息,如:
C#語言: public class Test{
??? public static void Main(string[] args)
??? {
??????? System.Reflection.MemberInfo info = typeof(Bar);
??????? CallTracingAttribute attribute = (CallTracingAttribute) Attribute.GetCustomAttribute(info,typeof(CallTracingAttribute));
??????? if (attribute != null)
??????? {
???????????? Console.WriteLine(“Tracing Information:{0}”,attribute.TracingInfo);
??????? }
??? }
}
4.2.2.3上下文(Context)和Attribute
所謂上下文(Context),是指一個邏輯上的執(zhí)行環(huán)境。每一個應(yīng)用程序域都有一個或多個Context,.Net中的所有對象都會在相應(yīng)的Context中創(chuàng)建和運(yùn)行。如圖4.1所示,它顯示了一個安全地存在于Context的對象:
?
圖4.1 安全地存在于Context的對象
在圖4.1中,上下文(Context)提供了錯誤傳播、事務(wù)管理和同步功能,而對象的創(chuàng)建和運(yùn)行就存在于該Context中。在.Net中,提供 了ContextBoundObject類,它代表的含義就是該對象應(yīng)存在于指定的Context邊界中(Object that will be bound with a context)。凡是繼承了ContextBoundObject類的類類型,就自動具備了對象與Context之間的關(guān)系。事實(shí)上,如果一個類對象沒 有繼承自ContextBoundObject,則該對象默認(rèn)會創(chuàng)建和運(yùn)行在應(yīng)用程序域的default context中,而繼承自ContextBoundObject的類對象,在其對象實(shí)例被激活時,CLR將自動創(chuàng)建一個單獨(dú)的Context供其生存。
如果需要判定ContextBoundObject類型對象所認(rèn)定的Context,只需要為該類型對象施加ContextAttribute即 可。ContextAttribute類繼承了Attribute類,它是一個特殊的Attribute,通過它,可以獲得對象需要的合適的執(zhí)行環(huán)境,即 Context(上下文)。同時,ContextAttribute還實(shí)現(xiàn)了IContextAttribute和IContextProperty接 口。
由于在施加Attribute時,只需要獲取ContextBoundObject類型的Context屬性,因此,我們也可以自定義 Attribute,只需要該自定義的Attribute實(shí)現(xiàn)IContextAttribute即可。IContextAttribute接口的定義如 下:
C#語言:? public interface IContextAttribute{
??? bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg);
??? void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg);
}
每個context attribute在context的構(gòu)造階段(通常是由ContextBoundObject對象構(gòu)造動作引發(fā)的)會被首先問到 IsContextOK,就是說新創(chuàng)建的這個ContextBoundObjec(通過ctorMsg可以知道是哪個對象的哪個構(gòu)造方法被用來構(gòu)造 ContextBoundObjec對象的)能不能在給定的ctx中存在?這個目的主要是減少應(yīng)用程序域中潛在的context的數(shù)量,如果某些 ContextBoundObjec類型可以共用一個有所需特性的執(zhí)行環(huán)境的話,就可以不用再創(chuàng)建新的環(huán)境,而只要在已有的環(huán)境中構(gòu)造并執(zhí)行就好了。
如果ContextBoundObjec類型上設(shè)置的所有context attributes都認(rèn)同給定的context(也即調(diào)用代碼所處的context)是正確地的(此時IsContextOK均返回true),那么新 的ContextBoundObjec就會被綁定到這個context上。否則,只有有一個attribute返回false,就會立即創(chuàng)建一個新的 context。然后,CLR會再一次詢問每一個context attribute新構(gòu)造的context是否正確,由于Context已經(jīng)被重新創(chuàng)建,通常此時返回的結(jié)果應(yīng)為false。那么,Context構(gòu)造程 序就會調(diào)用其GetPropertiesForNewContext()方法,context attribute可以用這個方法傳入的構(gòu)造器方法調(diào)用信息(ctorMsg)中的context properties列表(ContextProperties)來為新建的context增加所需的context properties。
從AOP的角度來看,Context類似于前面分析的橫切關(guān)注點(diǎn),那么利用我們自定義的Context Attribute,就可以獲得對象它所存在的上下文,從而建立業(yè)務(wù)對象與橫切關(guān)注點(diǎn)之間的關(guān)系。
4.2.3代理(Proxy)
在程序設(shè)計中使用代理(Proxy),最重要的目的是可以通過利用代理對象,實(shí)現(xiàn)代理所指向的真實(shí)對象的訪問。在GOF的《設(shè)計模式》中,將代理(Proxy)模式分為四種:
1、遠(yuǎn)程代理(Remote Proxy)。它為一個位于不同的地址空間的對象提供一個局域代表對象。這個不同的地址空間可以是在本機(jī)器中,亦可是在另一臺機(jī)器中。
2、虛代理(Virtual Proxy)。它能夠根據(jù)需要創(chuàng)建一個資源消耗較大的對象,使得此對象只在需要時才會被真正創(chuàng)建。
3、保護(hù)代理(Protection Proxy)。它控制對原始對象的訪問,如果需要可以給不同的用戶提供不同級別的使用權(quán)限。
4、 智能引用代理(Smart Reference Proxy)。它取代了簡單的指針,在訪問一個對象時,提供一些額外的操作。例如,對指向?qū)嶋H對象的引用計數(shù),這樣當(dāng)該對象沒有引用時,可以自動釋放它。 當(dāng)?shù)谝淮我靡粋€持久對象時,智能引用可以將該對象裝入內(nèi)存。在訪問一個實(shí)際對象前,檢查該對象是否被鎖定,以確保其他對象不能改變它。
在.Net Remoting中,采用了遠(yuǎn)程代理(Remote Proxy)模式。采用代理技術(shù),使得對象可以在兩個不同的應(yīng)用程序域(甚至可以是兩臺不同的機(jī)器)之間傳遞。代理在.Net中被分為透明代理 (Transparent Proxy)和真實(shí)代理(Real Proxy)。Transparent Proxy的目標(biāo)是在 CLR 中在 IL 層面最大程度扮演被代理的遠(yuǎn)端對象,從類型轉(zhuǎn)換到類型獲取,從字段訪問到方法調(diào)用。對 CLR 的使用者來說,Transparent Proxy和被其代理的對象完全沒有任何區(qū)別,只有通過 RemotingServices.IsTransparentProxy 才能區(qū)分兩者的區(qū)別。Real Proxy則是提供給 CLR 使用者擴(kuò)展代理機(jī)制的切入點(diǎn),通過從Real Proxy繼承并實(shí)現(xiàn) Invoke 方法,用戶自定義代理實(shí)現(xiàn)可以自由的處理已經(jīng)被從棧調(diào)用轉(zhuǎn)換為消息調(diào)用的目標(biāo)對象方法調(diào)用,如實(shí)現(xiàn)緩存、身份驗(yàn)證、安全檢測、延遲加載等等。
如果我們希望自己定義的代理類能夠“模仿”真實(shí)對象的能力,首先就需要實(shí)現(xiàn)透明代理。然而,CLR中雖然提供了這樣一個透明代理類 (_TransparentProxy),我們卻不能讓自己的代理類從透明代理類派生,也不能通過自定義Attribute、實(shí)現(xiàn)標(biāo)志性接口等方式將代理 類標(biāo)識為透明代理,從而讓CLR能夠認(rèn)識。要獲取透明代理,必須要提供一個真實(shí)代理。一個真實(shí)代理是一個從 System.Runtime.Remoting.Proxies.RealProxy派生而來的類。這個RealProxy類的首要功能就是幫我們在運(yùn) 行期動態(tài)生成一個可以透明兼容于某一個指定類的透明代理類實(shí)例。從RealProxy的源代碼,可以看出透明代理和真實(shí)代理之間的關(guān)系:
C#語言:? namespace System.Runtime.Remoting.Proxies{
? abstract public class RealProxy
? {
??? protected RealProxy(Type classToProxy) : this(classToProxy, (IntPtr)0, null){}
??? protected RealProxy(Type classToProxy, IntPtr stub, Object stubData)
??? {
????? if(!classToProxy.IsMarshalByRef && !classToProxy.IsInterface)
??????? throw new ArgumentException(...);
????? if((IntPtr)0 == stub)
????? {
??????? stub = _defaultStub;
??????? stubData = _defaultStubData;
????? }
????? _tp = null;
????? if (stubData == null)
??????? throw new ArgumentNullException("stubdata");
????? _tp = RemotingServices.CreateTransparentProxy(this, classToProxy, stub, stubData);
??? }
??? public virtual Object GetTransparentProxy()
??? {
????? return _tp;
??? }
? }
}
很明顯,透明代理(Transparent Proxy)是在RealProxy類的構(gòu)造函數(shù)中,調(diào)用RemotingServices.CreateTransparentProxy()方法動態(tài) 創(chuàng)建的。CreateTransparentProxy()方法將把被代理的類型強(qiáng)制轉(zhuǎn)換為統(tǒng)一的由 CLR 在運(yùn)行時創(chuàng)建的 RuntimeType 類型,進(jìn)而調(diào)用 Internal 方法完成TransparentProxy的創(chuàng)建。通過GetTransparentProxy()方法,就可以獲得創(chuàng)建的這個透明代理對象。因此,要定 義自己的真實(shí)代理對象,只需要繼承RealProxy類即可:
C#語言: using System.Runtime.Remoting.Proxies;public class MyRealProxy: RealProxy
{
? public MyRealProxy(Type classToProxy): base(classToProxy)
? {
?? // …
? }
}
透明代理和真實(shí)代理在上下文(Context)中,會起到一個偵聽器的作用。首先,透明代理將調(diào)用堆棧序列化為一個稱為消息(Message)的對 象,然后再將消息傳遞給真實(shí)代理。真實(shí)代理接收消息,并將其發(fā)送給第一個消息接收進(jìn)行處理。第一個消息接收對消息進(jìn)行預(yù)處理,將其繼續(xù)發(fā)送給位于客戶端和 對象之間的消息接收堆棧中的下一個消息接收,然后對消息進(jìn)行后處理。下一個消息接收也如此照辦,以此類推,直到到達(dá)堆棧構(gòu)建器接收,它將消息反序列化回調(diào) 用堆棧,調(diào)用對象,序列化出站參數(shù)和返回值,并返回到前面的消息接收。這個調(diào)用鏈如圖4.2所示。
?
圖4.2 代理(Proxy)偵聽消息的順序
由于透明代理完全等同于其代理的對象,因此,當(dāng)我們偵聽到代理對象被調(diào)用的消息時,就可以截取該消息,并織入需要執(zhí)行的方面邏輯,完成橫切關(guān)注邏輯與核心邏輯的動態(tài)代碼織入。
4.3 .Net平臺下AOP技術(shù)實(shí)現(xiàn)
4.3.1實(shí)現(xiàn)原理
根據(jù)對.Net中元數(shù)據(jù)(Metadata)、Attribute、上下 文(Context)、代理(Proxy)等技術(shù)要素的分析,要在.Net中實(shí)現(xiàn)AOP,首先需要獲得一個類對象的上下文(Context),則其前提就 是這個類必須從System.ContextBoundObject類派生。這個類對象就相當(dāng)于AOP中的核心關(guān)注點(diǎn),而類對象的上下文則屬于AOP的橫 切關(guān)注點(diǎn)。很顯然,只需要利用上下文,就可以方便的實(shí)現(xiàn)核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)的分離。
正如圖4.1所示,對象是存在于上下文中的。利用自定義Attribute,可以建立對象與上下文之間的關(guān)聯(lián)。Attribute可以擴(kuò)展對象的元 數(shù)據(jù),從而標(biāo)識出該對象屬于其中的一個或多個Aspect。一旦該對象實(shí)例被創(chuàng)建或調(diào)用時,就可以利用反射技術(shù)獲得該對象的自定義Attribute。為 使得對象的元數(shù)據(jù)與上下文關(guān)聯(lián)起來,就要求這個自定義的Attribute必須實(shí)現(xiàn)接口IContextAttribute。
獲得了對象的上下文之后,透明代理與真實(shí)代理就能夠?qū)υ搶ο蟮姆椒ㄕ{(diào)用(包括構(gòu)造函數(shù))進(jìn)行偵聽,并完成消息的傳遞。傳遞的消息可以被Aspect 截取,同時利用真實(shí)代理,也可以完成對業(yè)務(wù)對象的Decorate,將Aspect邏輯注入到業(yè)務(wù)對象中。由于在大型的企業(yè)系統(tǒng)設(shè)計中,橫切關(guān)注點(diǎn)會包括 事務(wù)管理、日志管理、權(quán)限控制等多方面,但由于方面(Aspect)在技術(shù)上的共同特性,我們可以利用.Net的相關(guān)技術(shù)實(shí)現(xiàn)方面(Aspect)的核心 類庫,所有的橫切關(guān)注點(diǎn)邏輯,都可以定義為派生這些類庫的類型,從而真正在.Net中實(shí)現(xiàn)AOP技術(shù)。
4.3.2 AOP公共類庫
4.3.2.1 AOP Attribute
如上所述,要實(shí)現(xiàn)AOP技術(shù),首先需要自定義一個Attribute。該自定義Attribute必須實(shí)現(xiàn)IContextAttribute,因此其定義如下所示:
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
[AttributeUsage(AttributeTargets.Class)]
public abstract class AOPAttribute:Attribute,IContextAttribute
{
??? private string m_AspectXml;
??? private const string CONFIGFILE = @"configuration\aspect.xml";
??? public AOPAttribute()???????????????????
??? {
??????? m_AspectXml = CONFIGFILE;
??? }??
??? public AOPAttribute(string aspectXml)
??? {
??????? this.m_AspectXml = aspectXml;
??? }?
??? protected abstract AOPProperty GetAOPProperty();
??? #region IContextAttribute Members
??? public sealed void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
??? {
??????? AOPProperty property = GetAOPProperty();???
??????? property.AspectXml = m_AspectXml;????
??????? ctorMsg.ContextProperties.Add(property);
??? }
??? public bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)
??? {
??????? return false;
??? }
}
類AOPAttribute除了繼承System.Attribute類之外,關(guān)鍵之處在于實(shí)現(xiàn)了接口IContextAttribute接口。接 口方法GetPropertiesForNewContext()其功能是向Context添加屬性(Property)集合,這個集合是 IConstructionCallMessage對象的ContextProperties屬性。而接口方法IsContextOK(),則用于判斷 Context中是否存在指定的屬性。這個方法會在Context的構(gòu)造階段(通常是由被施加了AOPAttribute的業(yè)務(wù)對象在創(chuàng)建時引發(fā)的)被調(diào) 用,如果返回false,會創(chuàng)建一個新的Context。
GetAOPProperty()方法是一個受保護(hù)的抽象方法,繼承AOPAttribute的子類將重寫該方法,返回一個AOPProperty 對象。在這里,我們利用了Template Method模式,通過該方法創(chuàng)建符合條件的AOPProperty對象,并被GetPropertiesForNewContext()方法添加到屬性 集合中。
抽象類AOPAttribute是所有與方面有關(guān)的Attribute的公共基類。所有方面的相關(guān)Attribute均繼承自它,同時實(shí)現(xiàn)GetAOPProperty()方法,創(chuàng)建并返回與之對應(yīng)的AOPProperty對象。
4.3.2.2 AOP Property
ContextProperties是一個特殊的集合對象,它存放的是對象被稱為Context Property,是一個實(shí)現(xiàn)了IContextProperty接口的對象,這個對象可以為相關(guān)的Context提供一些屬性。 IContextProperty接口的定義如下:
{
??? string Name { get; }
??? bool IsNewContextOK(Context newCtx);
??? void Freeze(Context newCtx);
}
IContextProperty接口的Name屬性,表示Context Property的名字,Name屬性值要求在整個Context中必須是唯一的。IsNewContextOK()方法用于確認(rèn)Context是否存在 沖突的情況。而Freeze()方法則是通知Context Property,當(dāng)新的Context構(gòu)造完成時,則進(jìn)入Freeze狀態(tài)(通常情況下,Freeze方法僅提供一個空的實(shí)現(xiàn))。
由于IContextProperty接口僅僅是為Context提供一些基本信息,它并不能完成對方法調(diào)用消息的截取。根據(jù)對代理技術(shù)的分析,要 實(shí)現(xiàn)AOP,必須在方法調(diào)用截取消息傳遞,并形成一個消息鏈Message Sink。因此,如果需要向所在的Context的Transparent Proxy/Real Proxy中植入Message Sink,Context Property還需要提供Sink的功能。所幸的是,.Net已經(jīng)提供了實(shí)現(xiàn)MessageSink功能的相關(guān)接口,這些接口的命名規(guī)則為 IContributeXXXSink,XXX代表了四種不同的 Sink:Envoy,ClientContext,ServerContext,Object。這四種接口有其相似之處,都只具有一個方法用于返回一個 IMessageSink對象。由于我們需要獲取的透明代理對象,是能夠穿越不同的應(yīng)用程序域的。在一個應(yīng)用程序域收到其他應(yīng)用程序域的對象,則該對象 在.Net中被稱為Server Object,該對象所處的Context也被稱為Server Context。我們在.Net中實(shí)現(xiàn)AOP,其本質(zhì)正是要獲得對象的Server Context,并截取該Context中的方法調(diào)用消息,因而Context Property對象應(yīng)該實(shí)現(xiàn)IContributeServerContextSink接口。事實(shí)上,也只有 IContributeServerContextSink接口的GetServerContextSink()方法,才能攔截包括構(gòu)造函數(shù)在內(nèi)的所有方 法的調(diào)用。
因此,AOP Property最終的定義如下:
C#語言:? using System;using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;
public abstract class AOPProperty : IContextProperty, IContributeServerContextSink
{
??? private string m_AspectXml;
??? public AOPProperty()
??? {
??????? m_AspectXml = string.Empty;?????????
??? }
??? public string AspectXml
??? {
??????? set { m_AspectXml = value; }
??? }
??? protected abstract IMessageSink CreateAspect(IMessageSink nextSink);
??? protected virtual string GetName()
??? {
??????? return "AOP";
??? }
??? protected virtual void FreezeImpl(Context newContext)
??? {
??????? return;
??? }
??? protected virtual bool CheckNewContext(Context newCtx)
??? {
??????? return true;
??? }
??? #region IContextProperty Members
??? public void Freeze(Context newContext)
??? {
??????? FreezeImpl(newContext);
??? }
??? public bool IsNewContextOK(Context newCtx)
??? {
??????? return CheckNewContext(newCtx);
??? }
??? public string Name
??? {
??????? get { return GetName(); }
??? }
??? #endregion
??? #region IContributeServerContextSink Members
??? public IMessageSink GetServerContextSink(IMessageSink nextSink)
??? {
??????? Aspect aspect = (Aspect)CreateAspect(nextSink);??????????
??????? aspect.ReadAspect(m_AspectXml,Name);??????????
??????? return (IMessageSink)aspect;
??? }
??? #endregion
}
在抽象類AOPProperty中,同樣利用了Template Method模式,將接口IContextProperty的方法的實(shí)現(xiàn)利用受保護(hù)的虛方法延遲到繼承AOPProperty的子類中。同時,對于接口 IContributeServerContextSink方法GetServerContextSink(),則創(chuàng)建并返回了一個Aspect類型的對 象,Aspect類型實(shí)現(xiàn)了IMessageSink接口,它即為AOP中的方面,是所有方面(Aspect)的公共基類。
AOPProperty類作為抽象類,是所有與上下文有關(guān)的Property的公共基類。作為Context Property應(yīng)與Aspect相對應(yīng),且具體的AOPProperty類對象應(yīng)在AOPAttribute的子類中創(chuàng)建并獲得。
4.3.2.3 Aspect與PointCut
Aspect類是AOP的核心,它的本質(zhì)是一個Message Sink,代理正是通過它進(jìn)行消息的傳遞,并截獲方法間傳遞的消息。Aspect類實(shí)現(xiàn)了IMessageSink接口,其定義如下:
{
??? IMessage SyncProcessMessage(IMessage msg);
??? IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink);
??? IMessageSink NextSink { get; }???
}
IMessageSink接口利用NextSink將多個Message Sink連接起來,以形成一個消息接收器鏈;而SyncProcessMessage()和AsyncProcessMessage()方法則分別用于同步和異步操作,它們在消息傳遞的時候被調(diào)用。
注意方法SyncProcessMessage()中的參數(shù),是一個IMessage接口類型的對象。在.Net 中,IMethodCallMessage和IMethodReturnMessage接口均繼承自IMessage接口,前者是調(diào)用方法的消息,而后者 則是方法被調(diào)用后的返回消息。利用這兩個接口對象,就可以獲得一個對象方法的切入點(diǎn)。因此,一個最簡單的Aspect實(shí)現(xiàn)應(yīng)該如下:
C#語言: public abstract class Aspect : IMessageSink{????
??? private IMessageSink m_NextSink;
??? public AOPSink(IMessageSink nextSink)
??? {
??????? m_NextSink = nextSink;???????
??? }
??? public IMessageSink NextSink
??? {
??????? get { return m_NextSink; }
??? }
??? public IMessage SyncProcessMessage(IMessage msg)
??? {
??????? IMethodCallMessage call = msg as IMethodCallMessage;
??????? if (call == null)
??????? {
???????????? return null;
??????? }
??????? IMessage retMsg = null;
??????? BeforeProcess();
??????? retMsg = m_NextSink.SyncProcessMessage(msg);
??????? AfterProcess();
??????? return retMsg;
??? }
??? public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
??? {
???????? return null;
??? }
??? private void BeforeProcess()
??? {
???????? //方法調(diào)用前的實(shí)現(xiàn)邏輯;
??? }
??? private void AfterProcess()
??? {
???????? //方法調(diào)用后的實(shí)現(xiàn)邏輯;
??? }
}
注意在方法SyncProcessMessage()中,IMessageSink對象m_NextSink通過Aspect構(gòu)造函數(shù)賦值為業(yè)務(wù)對 象的透明代理,在調(diào)用m_NextSink的SyncProcessMessage()方法時,此時調(diào)用的是與該透明代理對應(yīng)的真實(shí)代理。如果在消息接收 鏈中還存在代理,在方法調(diào)用將會沿著消息鏈不斷的向后執(zhí)行。而對于一個業(yè)務(wù)對象而言,此時的IMessage即為該對象中被調(diào)用的方 法,SyncProcessMessage(msg)就相當(dāng)于執(zhí)行了該方法。而在m_NextSink.SyncProcessMessage(msg) 方法前后執(zhí)行BeforeProcess()和AfterProcess(),就完成了對方法的截取,并將自己的Aspect邏輯織入到業(yè)務(wù)對象的方法調(diào)用中,從而實(shí)現(xiàn)了AOP。
然而對于AOP技術(shù)的實(shí)際應(yīng)用而言,并非業(yè)務(wù)對象的所有方法都需要被截取進(jìn)而進(jìn)行方面的織入。也即是說,切入點(diǎn)(PointCut)必須實(shí)現(xiàn)可被用 戶定義。而所謂切入點(diǎn),實(shí)際上是業(yè)務(wù)對象方法與Advice之間的映射關(guān)系。在.Net中,我們可以通過集合對象來管理這個映射關(guān)系。由于Advice包 括Before Advice和After Advice,因此,在Aspect類中應(yīng)該定義兩個集合對象:
C#語言:? private SortedList m_BeforeAdvices;private SortedList m_AfterAdvices;
在添加PointCut時,是將方法名和具體的Advice對象建立映射,根據(jù)SortedList集合的特性,我們將方法名作為SortedList的Key,而Advice則作為SortedList的Value:
C#語言:? protected virtual void AddBeforeAdvice(string methodName, IBeforeAdvice before){
??? lock (this.m_BeforeAdvices)
??? {
??????? if (!m_BeforeAdvices.Contains(methodName))
??????? {
??????????? m_BeforeAdvices.Add(methodName, before);
??????? }
??? }
}
protected virtual void AddAfterAdvice(string methodName, IAfterAdvice after)
{
??? lock (this.m_AfterAdvices)
??? {
??????? if (!m_AfterAdvices.Contains(methodName))
??????? {
??????????? m_AfterAdvices.Add(methodName, after);
??????? }
??? }
}
在向SortedList添加PointCut時,需要先判斷集合中是否已經(jīng)存在該P(yáng)ointCut。同時考慮到可能存在并發(fā)處理的情況,在添加PointCut時,利用lock對該操作進(jìn)行了加鎖,避免并發(fā)處理時可能會出現(xiàn)的錯誤。
建立了方法名和Advice的映射關(guān)系,在執(zhí)行SyncProcessMessage()方法,就可以根據(jù)IMessage的值,獲得業(yè)務(wù)對象被調(diào)用方法的相關(guān)屬性,然后根據(jù)方法名,找到其對應(yīng)的Advice,從而執(zhí)行相關(guān)的Advice代碼:
C#語言:? public IMessage SyncProcessMessage(IMessage msg){??????????
????? IMethodCallMessage call = msg as IMethodCallMessage;
????? string methodName = call.MethodName.ToUpper();
????? IBeforeAdvice before = FindBeforeAdvice(methodName);
????? if (before != null)
????? {
?????????? before.BeforeAdvice(call);
????? }??????????
????? IMessage retMsg = m_NextSink.SyncProcessMessage(msg);
????? IMethodReturnMessage reply = retMsg as IMethodReturnMessage;
????? IAfterAdvice after = FindAfterAdvice(methodName);
????? if (after != null)
????? {
??????????? after.AfterAdvice(reply);
????? }
????? return retMsg;
}
其中FindBeforeAdvice()和FindAfterAdvice()方法完成key和value的查找工作,分別的定義如下:
C#語言:? public IBeforeAdvice FindBeforeAdvice(string methodName){
??? IBeforeAdvice before;
??? lock (this.m_BeforeAdvices)
??? {
??????? before = (IBeforeAdvice)m_BeforeAdvices[methodName];
??? }
??? return before;
}
public IAfterAdvice FindAfterAdvice(string methodName)
{
??? IAfterAdvice after;
??? lock (this.m_AfterAdvices)
??? {
??????? after = (IAfterAdvice)m_AfterAdvices[methodName];
??? }
??? return after;
}
在找到對應(yīng)的Advice對象后,就可以調(diào)用Advice對象的相關(guān)方法,完成方面邏輯代碼的織入。
那么,PointCut是在什么時候添加的呢?我們可以在AOP的配置文件(Aspect.xml)中配置PointCut,然后在Aspect類 中,通過ReadAspect()方法,讀入配置文件,獲取PointCut以及Aspect需要的信息,包括方法名和Advice對象(通過反射動態(tài)創(chuàng) 建),在執(zhí)行AddBeforeAdvice()和AddAfterAdvice()方法將PointCut添加到各自的集合對象中:
C#語言:? public void ReadAspect(string aspectXml,string aspectName){
??? IBeforeAdvice before = (IBeforeAdvice)Configuration.GetAdvice(aspectXml,aspectName,Advice.Before);
??? string[] methodNames = Configuration.GetNames(aspectXml,aspectName,Advice.Before);
??? foreach (string name in methodNames)
??? {
???????? AddBeforeAdvice(name,before);
??? }
??? IAfterAdvice after = (IAfterAdvice)Configuration.GetAdvice(aspectXml,aspectName,Advice.After);
??? string[] methodNames = Configuration.GetNames(aspectXml,aspectName,Advice.After);
??? foreach (string name in methodNames)
??? {
???????? AddAfterAdvice(name,after);
??? }??
}
一個Aspect的配置文件示例如下:
XML語言:? <aop>??? <aspect value ="LogAOP">
??????? <advice type="before" assembly="AOP.Advice" class="AOP.Advice.LogAdvice">
???? <pointcut>ADD</pointcut>
???? <pointcut>SUBSTRACT</pointcut>
</advice>
<advice type="after" assembly="AOP.Advice" class="AOP.Advice.LogAdvice">
???? <pointcut>ADD</pointcut>
???? <pointcut>SUBSTRACT</pointcut>
</advice>
??? </aspect>
</aop>
配置文件中,元素Advice的assembly屬性和class屬性值,是利用反射創(chuàng)建Advice對象所需要的信息。另外,Aspect的名字 應(yīng)與方面的Property名保持一致,因?yàn)镽eadAspect()方法是通過AOPProperty名字來定位配置文件的Aspect。
4.3.2.4 Advice
在Aspect類中,已經(jīng)使用了Advice對象。根據(jù)類別不同,這些Advice對象分別實(shí)現(xiàn)IBeforeAdvice接口和IAfterAdvice接口:
using System.Runtime.Remoting.Messaging;
public interface IBeforeAdvice
{
??? void BeforeAdvice(IMethodCallMessage callMsg);
}
public interface IAfterAdvice
{
??? void AfterAdvice(IMethodReturnMessage returnMsg);
}
接口方法應(yīng)該實(shí)現(xiàn)具體的方面邏輯,同時可以通過IMethodCallMessage對象獲得業(yè)務(wù)對象的調(diào)用方法信息,通過IMethodReturnMessage對象獲得方法的返回信息。
4.4 .Net平臺AOP技術(shù)應(yīng)用案例
在4.3.2節(jié),我們已基本實(shí)現(xiàn)了AOP的公共類庫,這其中包括 AOPAttribute,AOPProperty,Aspect,IBeforeAdvice,IAfterAdvice。根據(jù)這些公共基類或接口,我 們就可以定義具體的方面,分別繼承或?qū)崿F(xiàn)這些類與接口。為了展示AOP在.Net中的應(yīng)用,在本節(jié),我將以一個簡單的實(shí)例來說明。
假定我們要設(shè)計一個計算器,它能提供加法和減法功能。我們希望,在計算過程中,能夠通過日志記錄整個計算過程及其結(jié)果,同時需要監(jiān)測其運(yùn)算性能。該 例中,核心業(yè)務(wù)是加法和減法,而公共的業(yè)務(wù)則是日志與監(jiān)測功能。根據(jù)前面對AOP的分析,這兩個功能作為橫切關(guān)注點(diǎn),將是整個系統(tǒng)需要剝離出來的“方面 ”。
4.4.1日志方面
???? 作為日志方面,其功能就是要截取業(yè)務(wù)對象方法的調(diào)用,并獲取之間傳遞的消息內(nèi)容。從上節(jié)的分析我們知道,方法間的消息可以從 IMethodCallMessage和IMethodReturnMessage接口對象獲得。因此,實(shí)現(xiàn)日志方面,最重要的是實(shí)現(xiàn)Aspect類中的 SyncProcessMessage()方法。此外,也應(yīng)定義與之對應(yīng)的Attribute和Property,以及實(shí)現(xiàn)日志邏輯的Advice。
4.4.1.1日志Attribute(LogAOPAttribute)
LogAOPAttribute類繼承AOPAttribute,由于AOPAttribute類主要是創(chuàng)建并獲得對應(yīng)的AOPProperty,因此,其子類也僅需要重寫父類的受保護(hù)抽象方法GetAOPProperty()即可:
public class LogAOPAttribute:AOPAttribute
{
?????? public LogAOPAttribute():base()
?????? {}
?????? public LogAOPAttribute(string aspectXml):base(aspectXml)
?????? {}
?????? protected override AOPProperty GetAOPProperty()
?????? {
????????????? return new LogAOPProperty();
?????? }??
}
通過對GetAOPProperty()方法的重寫,創(chuàng)建并獲得了與LogAOPAttribute類相對應(yīng)的LogAOPProperty,此時 在LogAOPAttribute所施加的業(yè)務(wù)對象的上下文中,所存在的AOP Property就應(yīng)該是具體的LogAOPProperty對象。
4.4.1.2日志Property(LogAOPProperty)
由于Context Property的名字在上下文中必須是唯一的,因此每個方面的Property的名字也必須是唯一的。因此在繼承AOPProperty的子類 LogAOPProperty中,必須重寫父類的虛方法GetName(),同時在LogAOPProperty中,還應(yīng)該創(chuàng)建與之對應(yīng)的Aspect, 也即是Message Sink,而這個工作是由抽象方法CreateAspect()來完成的。因此,LogAOPProperty類的定義如下:
{
?????? protected override IMessageSink CreateAspect(IMessageSink nextSink)
?????? {
????????????? return new LogAspect(nextSink);
?????? }
?????? protected override string GetName()
?????? {
????????????? return "LogAOP";
?????? }
}
為避免Property的名字出現(xiàn)重復(fù),約定成俗以方面的Attribute名為Property的名字,以本例而言,其Property名為LogAOP。
4.4.1.3日志Aspect(LogAspect)
LogAspect完成的功能主要是將Advice與業(yè)務(wù)對象的方法建立映射,并將 其添加到Advice集合中。由于我們在AOP實(shí)現(xiàn)中,利用了xml配置文件來配置PointCut,因此對于所有Aspect而言,這些操作都是相同 的,只要定義了正確的配置文件,將其讀入即可。對于Aspect的SyncProcessMessage(),由于攔截和織入的方法是一樣的,不同的只是 Advice的邏輯而已,因此在所有Aspect的公共基類中已經(jīng)提供了默認(rèn)的實(shí)現(xiàn):
{
?????? public LogAspect(IMessageSink nextSink):base(nextSink)
?????? {}??????????
}
然后定義正確的配置文件:
XML語言: <aspect value ="LogAOP">??? <advice type="before" assembly=" AOP.Advice" class="AOP.Advice.LogAdvice">
??????? <pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
??? </advice>
??? <advice type="after" assembly=" AOP.Advice" class="AOP.Advice.LogAdvice">
<pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
??? </advice>
</aspect>
LogAdvice所屬的程序集文件為AOP.Advice.dll,完整的類名為AOP.Advice.LogAdvice。
4.4.1.4日志Advice(LogAdvice)
由于日志方面需要記錄方法調(diào)用前后的相關(guān)數(shù)據(jù),因此LogAdvice應(yīng)同時實(shí)現(xiàn)IBeforeAdvice和IAfterAdvice接口:
{
??? #region IBeforeAdvice Members
??? public void BeforeAdvice(IMethodCallMessage callMsg)
??? {
??????? if (callMsg == null)
??????? {
??????????? return;
??????? }
??????? Console.WriteLine("{0}({1},{2})", callMsg.MethodName, callMsg.GetArg(0), callMsg.GetArg(1));
??? }
??? #endregion
??? #region IAfterAdvice Members
??? public void AfterAdvice(IMethodReturnMessage returnMsg)
??? {
??????? if (returnMsg == null)
??????? {
??????????? return;
??????? }
??????? Console.WriteLine("Result is {0}", returnMsg.ReturnValue);
??? }
??? #endregion
}
在BeforeAdvice()方法中,消息類型為IMethodCallMessage,通過這個接口對象,可以獲取方法名和方法調(diào)用的參數(shù)值。 與之相反,AfterAdvice()方法中的消息類型為IMethodReturnMessage,Advice所要獲得的數(shù)據(jù)為方法的返回值 ReturnValue。
4.4.2性能監(jiān)測方面
性能監(jiān)測方面與日志方面的實(shí)現(xiàn)大致相同,為簡便起見,我要實(shí)現(xiàn)的性能監(jiān)測僅僅是記錄方法調(diào)用前和調(diào)用后的時間。
4.4.2.1性能監(jiān)測Attribute(MonitorAOPAttribute)
與日志Attribute相同,MonitorAOPAttribute僅僅需要創(chuàng)建并返回對應(yīng)的MonitorAOPProperty對象:
public class MonitorAOPAttribute:AOPAttribute
{
?????? public MonitorAOPAttribute():base()
?????? {}
?????? public MonitorAOPAttribute(string aspectXml):base(aspectXml)
?????? {}
?????? protected override AOPProperty GetAOPProperty()
?????? {
????????????? return new MonitorAOPProperty();
?????? }
}
4.4.2.2性能監(jiān)測Property(MonitorAOPProperty)
MonitorAOPProperty的屬性名將定義為MonitorAOP,使其與日志方面的屬性區(qū)別。除定義性能監(jiān)測方面的屬性名外,還需要重寫CreateAspect()方法,創(chuàng)建并返回對應(yīng)的方面對象MonitorAspect:
{
?????? protected override IMessageSink CreateAspect(IMessageSink nextSink)
?????? {
????????????? return new MonitorAspect(nextSink);
?????? }
?????? protected override string GetName()
?????? {
????????????? return "MonitorAOP";
?????? }
}
4.4.2.3性能監(jiān)測Aspect(MonitorAspect)
MonitorAspect類的實(shí)現(xiàn)同樣簡單:
{
???????? public MonitorAspect(IMessageSink nextSink):base(nextSink)
???????? {}
}
而其配置文件的定義則如下所示:
XML語言:? <aspect value ="MonitorAOP">??? <advice type="before" assembly=" AOP.Advice" class="AOP.Advice.MonitorAdvice">
??????? <pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
??? </advice>
??? <advice type="after" assembly=" AOP.Advice" class="AOP.Advice.MonitorAdvice">
<pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
??? </advice>
</aspect>
MonitorAdvice所屬的程序集文件為AOP.Advice.dll,完整的類名為AOP.Advice.MonitorAdvice。
4.4.2.4性能監(jiān)測Advice(MonitorAdvice)
由于性能監(jiān)測方面需要記錄方法調(diào)用前后的具體時間,因此MonitorAdvice應(yīng)同時實(shí)現(xiàn)IBeforeAdvice和IAfterAdvice接口:
{
??? #region IBeforeAdvice Members
??? public void BeforeAdvice(IMethodCallMessage callMsg)
??? {
??????? if (callMsg == null)
??????? {
??????????? return;
??????? }
??????? Console.WriteLine("Before {0} at {1}", callMsg.MethodName, DateTime.Now);
??? }
??? #endregion
??? #region IAfterAdvice Members
??? public void AfterAdvice(IMethodReturnMessage returnMsg)
??? {
??????? if (returnMsg == null)
??????? {
??????????? return;
??????? }
??????? Console.WriteLine("After {0} at {1}", returnMsg.MethodName, DateTime.Now);
??? }
??? #endregion
}
MonitorAdvice只需要記錄方法調(diào)用前后的時間,因此只需要分別在BeforeAdvice()和AfterAdvice()方法中,記錄當(dāng)前的時間即可。
4.4.3業(yè)務(wù)對象與應(yīng)用程序
4.4.3.1業(yè)務(wù)對象(Calculator)
通過AOP技術(shù),我們已經(jīng)將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)完全分離,我們在定義業(yè)務(wù)對象時,并不需要關(guān)注包括日志、性能監(jiān)測等方面,這也是AOP技術(shù)的優(yōu)勢。當(dāng)然,由于要利用.Net中的Attribute及代理技術(shù),對于施加了方面的業(yè)務(wù)對象而言,仍然需要一些小小的限制。
首先,我們應(yīng)該將定義好的方面Aspect施加給業(yè)務(wù)對象。其次,由于代理技術(shù)要獲取業(yè)務(wù)對象的上下文(Context),該上下文必須是指定的, 而非默認(rèn)的上下文。上下文的獲得,是在業(yè)務(wù)對象創(chuàng)建和調(diào)用的時候,如果要獲取指定的上下文,在.Net中,要求業(yè)務(wù)對象必須繼承 ContextBoundObject類。因此,最后業(yè)務(wù)對象Calculator類的定義如下所示:
C#語言: [MonitorAOP][LogAOP]
public class Calculator : ContextBoundObject
{
?????? public int Add(int x,int y)
?????? {
????????????? return x + y;
?????? }
?????? public int Substract(int x,int y)
?????? {
????????????? return x - y;
?????? }
}
[MonitorAOP]和[LogAOP]正是之前定義的方面Attribute,此外Calculator類繼承了 ContextBoundObject。除此之外,Calculator類的定義與普通的對象定義無異。然而,正是利用AOP技術(shù),就可以攔截 Calculator類的Add()和Substract()方法,對其進(jìn)行日志記錄和性能監(jiān)測。而實(shí)現(xiàn)日志記錄和性能監(jiān)測的邏輯代碼,則完全與 Calculator類的Add()和Substract()方法分開,實(shí)現(xiàn)了兩者之間依賴的解除,有利于模塊的重用和擴(kuò)展。
4.4.3.2應(yīng)用程序(Program)
我們可以實(shí)現(xiàn)簡單的應(yīng)用程序,來看看業(yè)務(wù)對象Calculator施加了日志方面和性能檢測方面的效果:
{?????????
?????? [STAThread]
?????? static void Main(string[] args)
?????? {
????????????? Calculator cal = new Calculator();
????????????? cal.Add(3,5);
????????????? cal.Substract(3,5);
????????????? Console.ReadLine();
?????? }
}
程序創(chuàng)建了一個Calculator對象,同時調(diào)用了Add()和Substract()方法。由于Calculator對象被施加了日志方面和性能檢測方面,因此運(yùn)行結(jié)果會將方法調(diào)用的詳細(xì)信息和調(diào)用前后的運(yùn)行當(dāng)前時間打印出來,如圖4.3所示:
?
圖4.3 施加了方面的業(yè)務(wù)對象調(diào)用結(jié)果
如果要改變記錄日志和性能監(jiān)測結(jié)果的方式,例如將其寫到文件中,則只需要改變
總結(jié)
以上是生活随笔為你收集整理的AOP技术研究 再续的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 调用java接口_and
- 下一篇: phoenix的元数据一般存在哪里_Ph