[簡介]
面向方面編程(aop)是施樂公司帕洛阿爾托研究中心(xerox parc)在上世紀90年代發明的一種編程范式,它使開發人員可以更好地將本不該彼此糾纏在一起的任務(例如數學運算和異常處理)分離開來。aop方法有很多優點。首先,由于操作更為簡潔,所以改進了性能。其次,它使程序員可以花費更少的時間重寫相同的代碼。總之,aop能夠為不同過程提供更好的封裝性,提高未來的互操作性。
是什么使軟件工程師都希望自己能成為硬件工程師呢?自從函數發明以來,程序員花費了大量時間(及其老板的大多數資金)試圖設計這樣的系統:它們不過是一些組合模型,由其他人創建的部件構成,布置成獨特的形狀,再覆蓋上一些悅目的顏色。函數、模板、類、組件等等一切,都是軟件工程師自己創建“軟件集成電路”(模擬硬件設計師的電子器件)的種種嘗試。
我把這些都歸結于lego(樂高玩具)。把兩個玩具塊(即組件)拼起時發出的悅耳的咔噠聲很讓人上癮,會促使許多程序員發明一種又一種新的封裝和重用機制。這方面最新的進展就稱為面向方面編程(aop:aspect-oriented programming)。aop的核心是安排(一個摞在另一個之上)組件的一種方式,可以獲得其他種類基于組件的開發方法無法得到的重用級別。這種安排是在客戶端和對象之間的調用堆棧中進行的,其結果是為對象創建了一種特定的環境。這種環境正是aop程序員主要追求的東西,繼續閱讀本文,您將了解這一點。
隨本文一起提供的代碼示例分為兩部分:com部分和microsoft .net部分。com部分創建了一種基礎結構,可以在com對象中添加方面(編者按:英文原文為aspect,本文之后均將此詞譯為“方面”),提供用戶界面來配置類的方面,還給出了在我們提供的基礎結構上創建的一個示例方面實現。 .net部分說明了如何使用內置于.net基礎結構來完成com版本同樣的任務,但是所用代碼更少,選擇也更多。也提供了適合此基礎結構的示例方面。本文后面將講述所有這些代碼。
何謂aop?
一般情況下,對象是由一行行的代碼“粘合”在一起的。創建這個對象。創建那個對象。為那個對象(其值為這個對象)設置屬性。其間還點綴著一些用戶數據。將一切攪拌在一起。在運行時達到450攝氏度時就可以執行了。將多個組件以這種方式連接起來會出現這樣的問題:要實現不同的方法時,需要花費大量時間編寫同樣的代碼。這些代碼行中往往會有以下操作:將這個方法的活動記錄日志到一個文件中以備調試,運行安全性檢查,啟動一個事務,打開一個數據庫連接,記住捕捉c++異常或者win32結構化異常以轉換為com異常,還要驗證參數。而且,還要切記在方法執行完之后銷毀方法開始時的設置。
這種重復代碼經常出現的原因在于,開發人員被訓練為根據軟件新聞稿中的名詞來設計系統。如果設計的是銀行系統,account類和customer類必不可少,它們都將自己獨特的詳細信息收集到一處,但是它們的每個方法也都需要進行日志、安全檢查、事務管理等操作。區別在于,日志等操作是一些與特定應用無關的系統方面。人人都需要它們。人人都編寫這些代碼。人人都討厭這樣。
噢,并不是人人……人人都需要使用這些服務,人人都討厭編寫重復的代碼,但并不是人人都需要編寫這些代碼。例如,com+和.net程序員可以進行所謂的屬性化編程,也稱聲明性編程。這種技術允許程序員用屬性來修飾類型或者方法,聲明需要運行時提供某種服務。例如com+提供的一些服務,包括基于角色的安全性、實時激活、分布式事務管理和封送(marshaling)處理。在調用方法時,運行時(編者按:runtime,指.net framework提供的軟件運行環境)會放置一組在客戶端和服務器之間獲得的對象(對于com+程序員而言稱為“偵聽器”,對于.net程序員而言稱為“消息接收”),為每個方法提供服務,無需組件開發人員編寫任何代碼。這是面向方面編程最簡單的形式。
在aop的領域中,com+偵聽器就是通過元數據與組件相關聯的一些方面。運行時使用元數據來構造這組方面,通常是在創建對象時進行的。當客戶端調用方法時,那些特殊的方面依次獲得了處理調用、執行其服務的機會,最后再調用對象的方法。返回時,每個方面又有機會進行展開。這樣,就可以將每個組件的每個方法中都要編寫的同樣代碼行提取出來并放在各個方面中,讓運行時來放置它們。這組方面共同為組件方法的執行提供了上下文。上下文在環境中提供了方法的實現,其中的操作具有特殊的意義。
例如,圖1顯示了一個安全地存在于上下文中的對象,該上下文提供了錯誤傳播、事務管理和同步。win32控制臺應用程序中的程序能夠假定在上下文中存在控制臺,而且調用printf的結果將顯示在控制臺上,與之一樣,aop對象也可以假設事務已經建立,且該事務包含調用數據庫這一部分。如果設置這些服務出現了問題(例如沒有足夠資源建立事務),對象將不會被調用,因此也就無須擔心了。
常規用途aop
雖然com+提供了aop所需的大多數服務,但是若要用來作為常規用途aop環境,它還缺乏一個必需的重要細節:定義自定義方面的能力。例如,如果基于角色的安全性不適合的話,就不能實現基于角色扮演的安全性(如同讓最危險的人保護自己的對象)。如果程序員有這種能力,許多com慣用法都可以用aop框架實現。圖2提供了示例的簡短列表。
異常和錯誤處理
事務管理
日志的方法調用
異步方法調用
安全檢查
擴展二進制組件自動化,使之具有expando對象的能力
類似eiffel語言的契約式設計
參數校驗
圖2 針對aop的com慣用法
設計方面框架
當然,有了這樣的框架構思之后,我們還必須把它構建出來。我們希望這個框架具備以下功能:
q將客戶端和對象之間的方面串聯起來的運行時。
q用戶定義的方面,以com組件實現。
q有關哪個方面與每個com組件相關聯的元數據描述,如com+目錄一樣。
q在方面就緒時客戶端可以用來激活組件的方法。
有關aop框架的概念很簡單。關鍵就是偵聽和委托。偵聽的技巧在于,讓調用者相信它持有的接口指針指向它所請求的對象,而實際上這是一個指向偵聽器的指針,可以通過本文后面講述的激活技術之一獲取該偵聽器。偵聽器需要實現與目標組件相同的接口,但是需要通過與組件相關聯的方面堆棧來委托所有調用。在調用方法時,偵聽器將為每個方面提供預處理和后處理調用的機會,以及傳播或者取消當前調用的能力。
aop框架執行兩個不同的步驟,組件激活和方法調用。在組件激活中,aop框架構建方面實現對象的堆棧,并返回一個偵聽器的引用,而不是實際對象的引用。在方法調用中,當調用者對偵聽器進行方法調用時,偵聽器將調用委托給所有已經注冊的方面,從而對調用堆棧中的[in]和[in,out]參數進行預處理,并將實際調用提供給對象。然后通過傳遞組件返回的調用返回值,以及調用堆棧上的[in,out]和[out]參數,將調用提供給方面進行后處理。
作為com對象的方面
在我們的aop框架中,方面是實現了iaspect接口的com類,如圖3所示。
interface iaspect : iunknown {
hresult preprocess( [in] iunknown* punkdelegatee,
[in] bstr riid,
[in] bstr strmethodname,
[in] long nvtblslot,
[in] iunknown* penum);
hresult postprocess([in]hresult hroriginal,
[in] iunknown* punkdelegatee,
[in] bstr riid,
[in] bstr strmethodname,
[in] long nvtblslot,
[in] iunknown* penum);
}
框架在將方法調用傳遞給實際的底層組件實例(以下稱之為被委托者)之前,會調用所有指定方面的iaspect::preprocess方法。它把被委托者的身份、接口的iid、方法名、方法發生的vtbl槽以及對[in]和[in,out]參數的枚舉器傳遞給相應的方面。如果方面從preprocess返回故障hresult,框架就不把調用提供給被委托者,實際上是取消了調用。
方面預處理成功返回后,框架將實際調用提供給被委托者。無論是否從被委托者返回hresult,框架都會調用iaspect::postprocess方法,傳遞被委托者返回的hresult和postprocess方法需要的所有參數,只不過這一次枚舉器是建立在[out], [in,out]和[out,retval]參數之上的。
圖4顯示了如何編寫調用跟蹤方面,它能夠跟蹤傳遞給被委托者方法的所有調用者提供的參數。
圖4 call-tracing aspect
class ccalltracingaspect : public iaspect, ... {
public:
begin_category_map(ccalltracingaspect)
implemented_category(catid_aspects)
end_category_map()
stdmethodimp preprocess(...)
{ return dumpstack ( true, riid, strmethodname, penum ) ; }
stdmethodimp postprocess(...)
{ return dumpstack ( false, riid, strmethodname, penum ) ; }
hresult dumpstack(bool preprocess, bstr riid,
bstr strmethodname, iunknown *penum) {
if (preprocess) atltrace("preprocessing: %s(", strmethodname);
else atltrace("postprocessing: %s(", strmethodname);
ccomptr<ienumvariant> spenumvar;
penum->queryinterface(&spenumvar);
ccomvariant v;
bool bneedcomma = false;
while (spenumvar->next(1, &v, 0) == s_ok) {
if (bneedcomma) atltrace(", ");
else bneedcomma = true;
atltrace("%s", tostring(v));
}
atltrace(")/n");
return s_ok ;
}
...
};
既然已經有了一個用來調用方面的框架和一個可以使用的方面,我們需要一種機制將它們串聯起來。這種操作在對象激活時進行。
對象激活
盡管我們要將客戶端和對象之間任意數量的方面堆放起來,客戶端應該可以創建對象并調用其方法,正如沒有偵聽時的方式一樣。糟糕的是,com如果不采取一些奇特的技術手段(這正是microsoft事務服務在集成到com基礎結構中并改名為com+之前必須實現的),就無法支持在其主激活api cocreateinstance中注入的任意擴展性代碼。但是,com確實提供了完全擴展的激活api:visual basic中的getobject(對于c++程序員是cogetobject)。我們使用自定義名字對象基于該api構建aop激活代碼。
com名字對象是將任意字符串(稱為顯示名)轉換為com對象的一段代碼,這意味著可以創建一個新的,或者從文件中找到一個,甚至從月球上下載。我們的aop名字對象獲得元數據(描述與此處談論的類相關聯的方面),創建該類的實例,構造方面堆棧,通過aop偵聽器將它們串聯在一起,然后將偵聽器返回給客戶端。下面是一個示例:
private sub form_load()
set myfoo = getobject("aoactivator:c:/aopfoo.xml")
myfoo.dosomethingfooish
end sub
請注意,除了獲取foo的實例,客戶端使用組件不需要任何特殊操作。盡管aopfoo.xml文件會將任意數量的方面與foo的該特定實例關聯起來,它還是實現相同的接口,更重要的是,它具有相同的語義。
實現自定義com名字對象在某種意義上是一種神奇的技術,主要涉及以前ole細節的內幕知識。幸運的是,實現的大部分內容都是陳詞濫調,而com社區很久以前就把名字對象的基本實現都寫進了一個稱為ccommoniker的atl類中。(可以訪問 http://www.sellsbrothers.com/tools獲取該com名字對象框架。)使用這個框架,我們真正需要關心的就是實現parsedisplayname(這是一個分析自定義顯示名語法的枯燥方法)和bindtoobject(屬于名字對象的一部分,該名字對象用于激活客戶端提供的顯示名稱所指示的com對象)(參見圖5)。
圖5 creating the object in bindtoobject
stdmethodimp caopfactory::bindtoobject(
ibindctx* pbc, imoniker* pmktoleft, refiid riidresult,
void** ppvresult) {
// parsedisplayname has already pulled in the metadata from
// the xml file supplied by the client
// create the object to be hosted in our aop environment
ccomptr<iunknown> spcomp;
hresult hr = spcomp.cocreateinstance ( m_clsid ) ;
if (failed(hr)) return hr ;
// create our interceptor and return it the client
... // magic happens...
}
請注意,圖5中的代碼沒有顯示最困難的部分——偵聽器的創建和初始化。之所以困難,不在于偵聽器本身,而是偵聽器必須要做的工作。請記住,我們的通用aop框架要想能發揮作用,必須能夠用與所包裝的任何組件完全相同的一組接口來響應queryinterface方法。而返回的接口必須能夠獲得每個方法的客戶端所提供的調用堆棧,將其傳遞給所有方面,并一直傳遞到組件本身,保持參數完整-無論有多少,以及是何種類型。這是一項非常困難的任務,涉及到大量的__declspec(naked)和asm thunk。
幸運的是,因為com社區已經非常成熟,我們得以站在巨人的肩膀上使用通用委托器(ud),一個由keith brown創建的執行這一任務的com組件。keith曾在msj中分兩部分撰文描述了其ud,文章名為“building a lightweight com interception framework, part i:the universal delegator”(http://www.microsoft.com/msj/0199/intercept/intercept.aspx[l1]),和part ii:“the guts of the ud”(http://www.microsoft.com/msj/0299/intercept2/intercept2.aspx[l2])。我們可用keith的ud實現aop框架,這減少了bindtoobject實現中“神奇”的部分,如圖6所示。
圖6 rest of bindtoobject implementation
stdmethodimp caopfactory::bindtoobject(
ibindctx* pbc, imoniker* pmktoleft, refiid riidresult,
void** ppvresult) {
...
// create our interceptor and return it the client
// create and initialize our hook
ccomobject<chook>* phook ;
hr = phook->createinstance(&phook) ;
if (failed(hr)) return hr ;
ccomptr<idelegatorhookqi> sphook ;
hr = phook->queryinterface(&sphook) ;
if (failed(hr)) return hr ;
hr = phook->setaspects ( m_displayname, m_aspects.size(),
&m_aspects[0] ) ;
if (failed(hr)) return hr ;
// create the udfactory
ccomptr<idelegatorfactory> spdel ;
hr = cogetclassobject ( __uuidof(codelegator21), clsctx_inproc, 0,
__uuidof(idelegatorfactory), (void **)&spdel );
if (failed(hr)) return hr ;
// create the interceptor
hr = spdel->createdelegator(0, spcomp, 0, sphook, 0,
riidresult, ppvresult);
}
為了包裝目標組件供客戶端使用,執行以下四個步驟:
1. 使用組件的clsid創建實際組件,該clsid傳遞給原來在元數據xml文件中的名字對象。
2. 創建了一個delegatorhook對象,偵聽對對象的queryinterface調用。掛鉤負責將方法調用路由到每個方面。
3. 接下來,創建ud對象,檢索idelegatorfactory接口。
4. 使用idelegatorfactory調用createdelegator,傳遞實際對象的接口、委托器掛鉤、源調用者請求的接口的iid (riidresult),以及指向接口指針的指針(ppvresult)。委托器返回指向偵聽器的指針,可以調用每個調用的委托器掛鉤。
結果如圖7所示。由此開始,客戶端可以將偵聽器用作目標組件的實際接口指針。調用后,它們沿著路徑上的方面路由到目標組件。
aspect builder
為了激活組件,并使所有方面都正確的串聯起來,我們的aop名字對象依賴一個xml文件來描述組件和相關聯的方面。其格式非常簡單,只包含組件的clsid和方面組件的clsid。圖8的示例包裝了帶有兩個方面的microsoft flexgrid control。
圖8 wrapping the microsoft flexgrid control
<aopframework>
<component name="microsoft flexgrid control, version 6.0">
<clsid>{6262d3a0-531b-11cf-91f6-c2863c385e30}</clsid>
</component>
<aspects>
<aspect name="call-tracing aspect">
<clsid>{49efa33a-fdb2-4aed-807e-4d447d096642}
</clsid>
</aspect>
<aspect name="synchronization aspect">
<clsid>{6dba0579-8846-46a2-beff-382725a1022c}
</clsid>
</aspect>
</aspects>
</aopframework>
為了簡化aop元數據實例的創建任務,我們創建了aspect builder(如圖9所示)。
aspect builder會枚舉機器上注冊的所有方面,并在右邊的列表視圖中用云狀圖將它們都顯示出來。aspect builder的客戶端區域包含組件的圖形表示。可以雙擊它(或者使用相應的菜單項)并指定組件的progid。選擇了一個組件之后,可以將方面拖放到客戶端區域中,將方面添加到組件的aop元數據中。
要生成提供給aop名字對象所必需的xml格式,可以在“tools”菜單中選擇“compile”菜單項,元數據就會顯示在底部窗格中。可以在verify aspects窗格中實際編寫腳本,驗證元數據確實正確。可以將已經編譯的xml實例保存在磁盤上,也可以用aspect builder重載它們。
.net中的方面
盡管aspect builder使工作大大簡化,但是由于方面元數據與組件分開存儲,這使得在com中進行aop編程并不方便。糟糕的是,com的元數據在擴展性方面缺乏很多必要的功能,這正是我們感到首先需要將元數據和類分開存儲的原因。但是,作為com顯然的繼承者,.net就沒有這種問題。.net的元數據是完全可擴展的,因此具備所有必要的基礎,可以將方面通過屬性直接與類本身相關聯。例如,給定一個自定義的 .net屬性,我們就可以輕松地將調用跟蹤屬性與.net方法相關聯:
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和訪問方法時輸出的字符串。這是將自定義元數據與bar的兩個方法相關聯的屬性語法。
與com中的aop框架一樣,.net中的屬性根據.net中的組件分類。.net中的自定義屬性是用派生自attribute的類實現的,如下所示:
using system;
using system.reflection;
[attributeusage( attributetargets.classmembers,
allowmultiple = false )]
public class calltracingattribute : attribute {
public calltracingattribute(string s) {
console.writeline(s);
}
}
我們的屬性類本身也有屬性,這些屬性會修改其行為。在這種情況下,我們要求該屬性只與方法,而不是程序集、類或者字段關聯,并且每個方法只能有一個跟蹤屬性與之關聯。
將屬性與方法關聯以后,我們就成功了一半。為了提供aop功能,還需要在每個方法建立執行組件所必需的環境的前后訪問調用堆棧。這就需要一個偵聽器,以及組件賴以生存的上下文。在com中,我們要求客戶端使用aop名字對象激活組件,從而實現該任務。幸運的是,.net已經內置了掛鉤,因此客戶端無須做任何特殊的工作。
上下文綁定對象
.net中偵聽的關鍵(與com中一樣)在于要為com組件提供上下文。在com+和自定義的aop框架中,通過在客戶端和對象之間堆放方面,在方法執行之前為組件建立上下文的屬性來提供上下文。而在.net中,將為任何從system .contextboundobject派生的類提供上下文:
public class liketolivealone : contextboundobject {...}
當liketolivealone類的實例被激活時,.net運行時將自動創建一個單獨的上下文供其生存,并建立一個偵聽器,可以從中掛起我們自己的方面。.net偵聽器是兩個對象的組合-透明代理和真實代理。透明代理的行為與目標對象相同,與com aop偵聽器也一樣,它將調用堆棧序列化為一個稱為消息的對象,然后再將消息傳遞給真實代理。真實代理接收消息,并將其發送給第一個消息接收進行處理。第一個消息接收對消息進行預處理,將其繼續發送給位于客戶端和對象之間的消息接收堆棧中的下一個消息接收,然后對消息進行后處理。下一個消息接收也如此照辦,以此類推,直到到達堆棧構建器接收,它將消息反序列化回調用堆棧,調用對象,序列化出站參數和返回值,并返回到前面的消息接收。這個調用鏈如圖10所示。
為了參與到這個消息接收鏈中,我們首先需要從contextattribute(而不只是attribute)派生屬性,并提供所謂上下文屬性,將屬性更新為參與上下文綁定對象:
[attributeusage(attributetargets.class)]
public class calltracingattribute : contextattribute {
public calltracingattribute() :
base("calltrace") {}
public override void
getpropertiesfornewcontext
(iconstructioncallmessage ccm) {
ccm.contextproperties.add(new
calltracingproperty());
}
...
}
當激活這個對象時,為每個上下文屬性調用getpropertiesfornewcontext方法。這樣我們就能將自己的上下文屬性添加到與為對象創建的新上下文關聯的屬性列表中。上下文屬性允許我們將消息接收與消息接收鏈中的對象關聯起來。屬性類通過實現icontextobject和icontextobjectsink作為方面消息接收的工廠:
public class calltracingproperty : icontextproperty,
icontributeobjectsink {
public imessagesink getobjectsink(marshalbyrefobject o,
imessagesink next) {
return new calltracingaspect(next);
}
...
}
代理創建屬性的過程如圖11所示,其中先創建上下文屬性,然后創建消息接收。
.net方面
當一切都正確附加后,每個調用都會進入方面的imessagesink實現。syncprocessmessage允許我們預處理和后處理消息,如圖12所示。
圖12 .net call-tracing aspect
internal class calltracingaspect : imessagesink {
private imessagesink m_next;
private string m_typeandname ;
internal calltracingaspect(imessagesink next) {
// cache the next sink in the chain
m_next = next;
}
public imessage syncprocessmessage(imessage msg) {
preprocess(msg);
imessage returnmethod = m_next.syncprocessmessage(msg);
postprocess(msg, returnmethod);
return returnmethod;
}
private void preprocess(imessage msg) {
// we only want to process method calls
if (!(msg is imethodmessage)) return;
imethodmessage call = msg as imethodmessage;
type t = type.gettype(call.typename) ;
m_typeandname = t.name + "." + call.methodname ;
console.write("preprocessing: " + m_typeandname + "(");
// loop through the [in] parameters
for (int i = 0; i < call.argcount; ++i) {
if (i > 0) console.write(", ");
console.write(call.getargname(i) + "= " + call.getarg(i));
}
console.writeline(")");
// set us up in the callcontext
call.logicalcallcontext.setdata(contextname, this);
}
private void postprocess(imessage msg, imessage msgreturn)
{
// we only want to process method return calls
if (!(msg is imethodmessage) ||
!(msgreturn is imethodreturnmessage)) return;
imethodreturnmessage retmsg = (imethodreturnmessage)msgreturn;
console.write("postprocessing: ");
exception e = retmsg.exception;
if (e != null) {
console.writeline("exception was thrown: " + e);
return;
}
// loop through all the [out] parameters
console.write(m_typeandname + "(");
if (retmsg.outargcount > 0) {
console.write("out parameters[");
for (int i = 0; i < retmsg.outargcount; ++i ) {
if (i > 0) console.write(", ");
console.write(retmsg.getoutargname(i) + "= " +
retmsg.getoutarg(i));
}
console.write("]");
}
if (retmsg.returnvalue.gettype() != typeof(void))
console.write("returned [" + retmsg.returnvalue + "]");
console.writeline(")");
}
...
}
最后,希望將自己與調用跟蹤方面相關聯的上下文綁定類使用calltracingattribute聲明其首選項:
[aop.experiments.calltracingattribute()]
public class traceme : contextboundobject {
public int returnfive(string s) {
return 5;
}
}
請注意,我們把上下文屬性與類而非每個方法相關聯。.net上下文體系結構將自動通知我們每個方法,因此我們的調用跟蹤屬性擁有所有需要的信息,這為我們避免了以前處理普通屬性時,需要手工將屬性和每個方法相關聯的麻煩。當客戶端類實例化類并調用一個方法時,方面就被激活了:
public class client {
public static void main() {
traceme traceme = new traceme();
traceme.returnfive("stringarg");
}
}
運行時,客戶端和面向方面的對象輸出如下內容:
preprocessing: traceme.returnfive(s= stringarg)
postprocessing: traceme.returnfive( returned [5])
方面和上下文
迄今為止,我們這個簡單的方面還沒能真正實現預期的aop理想。盡管方面確實可以用來對方法調用進行單獨的預處理和后處理,但真正有趣的還是方面對方法執行本身的影響。例如,com+事務性方面會使對象方法中使用的所有資源提供程序參與同一個事務,這樣方法就可以僅通過中止com+事務性方面提供的事務來中止所有活動。為此,com+方面增加了com調用上下文,后者為有興趣訪問當前事務的所有組件提供了聚集點。同樣,.net也提供了可以用來允許方法參與的可擴展的調用上下文。例如,可以通過將自身置于.net上下文中,使對象(它封裝在調用跟蹤方面中)能夠在跟蹤消息流中添加信息,如下所示:
internal class calltracingaspect : imessagesink {
public static string contextname {
get { return "calltrace" ; }
}
private void preprocess(imessage msg) {
...
// set us up in the call context
call.logicalcallcontext.setdata(contextname, this);
}
...
}
一旦將方面添加到調用上下文后,方法可以再次將其抽出,并參與到跟蹤中:
[calltracingattribute()]
public class traceme: contextboundobject
public int returnfive(string s)
object obj =
callcontext.getdata(calltracingaspect.contextname) ;
calltracingaspect aspect = (calltracingaspect)obj ;
aspect.trace("inside methodcall");
return 5;
}
通過提供一種方式來增加調用上下文,.net允許方面為對象設置真實的環境。在我們的例子中,允許對象向流添加跟蹤語句,而無需知道流的目的地、如何建立流以及何時銷毀流,這很像com+的事務性方面,如下所示:
preprocessing: traceme.returnfive(s= stringarg)
during: traceme.returnfive: inside methodcall
postprocessing: traceme.returnfive( returned [5])
小結
借助面向方面編程,開發人員可以用與封裝組件本身相同的方式跨組件封裝公共服務的使用。通過使用元數據和偵聽器,可以在客戶端和對象之間放置任意服務,這種操作在com中是半無縫的,在.net中是無縫的。本文介紹的方面可以在進出方法調用的過程中訪問調用堆棧,它們提供增加了的上下文,供對象在其中生存。雖然與結構化編程或者面向對象編程相比aop尚不成熟,但是.net中對aop的本地支持為我們追求像樂高玩具那樣的軟件夢想提供了寶貴的工具。