Attribute在.NET程式設計的應用(六) (轉)

gugu99發表於2007-08-15
Attribute在.NET程式設計的應用(六) (轉)[@more@]

Attribute在的應用(六)


(承上節) 攔截機制的設計中,在客戶端和之間,存在著多種訊息接收器,這些訊息接收器組成一個連結串列,客戶端的物件的過程以及呼叫返回實行攔截,你可以定製自己的訊息接收器,把它們插入了到連結串列中,來完成你對一個呼叫的前處理和後處理。那麼呼叫攔截是如何構架或者說如何實現的呢?

在.NET中有兩種呼叫,一種是跨應用域(App ain),一種是跨上下文環境(Context),兩種呼叫均透過中間的(),代理被分為兩個部分:透明代理和實際代理。透明代理暴露同物件一樣的公共入口點,當客戶呼叫透明代理的時候,透明代理把堆疊中的幀轉換為訊息(上一節提到的實現IMessage介面的物件),訊息中包含了方法名稱和引數等屬性集,然後把訊息傳遞給實際代理,接下去分兩種情況:在跨應用域的情況下,實際代理使用一個格式化器對訊息進行序列化,然後放入通道中;在跨上下文環境的情況下,實際代理不必知道格式化器、通道和Context攔截器,它只需要在向前傳遞訊息之前對呼叫實行攔截,然後它把訊息傳遞給一個訊息接收器(實現IMessageSink的物件),每一個接收器都知道自己的下一個接收器,當它們對訊息進行處理之後(前處理),都將訊息傳遞給下一個接收器,一直到連結串列的最後一個接收器,最後一個接收器被稱為堆疊建立器,它把訊息還原為堆疊幀,然後呼叫物件,當呼叫方法結果返回的時候,堆疊建立器把結果轉換為訊息,傳回給呼叫它的訊息接收器,於是訊息沿著原來的連結串列往回傳,每個連結串列上的訊息接收器在回傳訊息之前都對訊息進行後處理。一直到連結串列的第一個接收器,第一個接收器把訊息傳回給實際代理,實際代理把訊息傳遞給透明代理,後者把訊息放回到客戶端的堆疊中。從上面的描述我們看到穿越Context的訊息不需要格式化,CLR使用一個內部的通道,叫做CrosntextChannel,這個物件也是一種訊息接收器。

有幾種訊息接收器的型別,一個呼叫攔截可以在端進行也可以在客戶端進行,伺服器端接收器攔截所有對伺服器上下文環境中物件的呼叫,同時作一些前處理和後處理。客戶端的接收器攔截所有外出客戶端上下文環境的呼叫,同時也做一些前處理和後處理。伺服器負責伺服器端接收器的,攔截對伺服器端上下文環境訪問的接收器稱為伺服器上下文環境接收器,那些攔截呼叫實際物件的接收器是物件接收器。透過客戶安裝的客戶端接收器稱為客戶端上下文環境接受器,透過物件安裝的客戶端接收器則稱為特使(Envoy)接收器,特使接收器僅攔截那些和它相關的物件。客戶端的最後一個接收器和伺服器端的第一個接收器是CrossContextChannel型別的例項。不同型別的接收器組成不同的段,每個段的端點都裝上稱為終結器的接收器,終結器起著把本段的訊息傳給下一個段的作用。在伺服器上下文環境段的最後一個終結器是ServerContextTenatorSink。如果你在終結器呼叫NextSink,它將返回一個null,它們的行為就像是死端頭,但是在它們內部儲存有下一個接收器物件的私有欄位。

我們大致介紹了.NET Framework的物件呼叫攔截的實現機制,目的是讓大家對這種機制有一個認識,現在是實現我們程式碼的時候了,透過程式碼的實現,你可以看到訊息如何被處理的過程。首先是為我們的定義一個接收器CallTraceSink:


//TraceContext.cs using System; using System.Runtime.Remoting.Contexts; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Activation; namespace NiwalkerDemo { public class CallTraceSink : IMessageSink //實現IMessageSink { private IMessageSink nextSink; //儲存下一個接收器 //在構造器中初始化下一個接收器 public CallTraceSink(IMessageSink next) { nextSink=next; } //必須實現的IMessageSink介面屬性 public IMessageSink NextSink { get { return nextSink; } } //實現IMessageSink的介面方法,當訊息傳遞的時候,該方法被呼叫 public IMessage SyncProcessMessage(IMessage msg) { //攔截訊息,做前處理 Preprocess(msg); //傳遞訊息給下一個接收器 IMessage retMsg=nextSink.SyncProcessMessage(msg); //呼叫返回時進行攔截,並進行後處理 Postprocess(msg,retMsg); return retMsg; } //IMessageSink介面方法,用於非同步處理,我們不實現非同步處理,所以簡單返回null, //不管是同步還是非同步,這個方法都需要定義 public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) { return null; } //我們的前處理方法,用於檢查庫存,出於簡化的目的,我們把檢查庫存和傳送都寫在一起了, //在實際的實現中,可能也需要把Inventory物件繫結到一個上下文環境, //另外,可以將傳送郵件設計為另外一個接收器,然後透過NextSink進行安裝 private void Preprocess(IMessage msg) { //檢查是否是方法呼叫,我們只攔截Order的Submit方法。 IMethodCallMessage call=msg as IMethodCallMessage; if(call==null) return; if(call.MethodName=="Submit") { string product=call.GetArg(0).ToString(); //獲取Submit方法的第一個引數 int qty=(int)call.GetArg(1); //獲取Submit方法的第二個引數 //呼叫Inventory檢查庫存存量 if(new Inventory().Checkout(product,qty)) Console.WriteLine("Order availible"); else { Console.WriteLine("Order unvailible"); SendE(); } } } //後處理方法,用於記錄訂單提交資訊,同樣可以將記錄作為一個接收器 //我們在這裡處理,僅僅是為了演示 private void Postprocess(IMessage msg,IMessage retMsg) { IMethodCallMessage call=msg as IMethodCallMessage; if(call==null) return; Console.WriteLine("Log order information"); } private void Send() { Console.WriteLine("Send email to manager"); } } ...

接下來我們定義上下文環境的屬性,上下文環境屬性必須根據你要建立的接收器型別來實現相應的介面,比如:如果建立的是伺服器上下文環境接收器,那麼必須實現IContributeServerContextSink介面。 

... public class CallTraceProperty : IContextProperty, IContributeSink { public CallTraceProperty() { } //IContributeObjectSink的介面方法,例項化訊息接收器 public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next) { return new CallTraceSink(next); } //IContextProperty介面方法,如果該方法返回ture,在新的上下文環境中啟用物件 public bool IsNewContextOK(Context newCtx) { return true; } //IContextProperty介面方法,提供高階使用 public void Freeze(Context newCtx) { } //IContextProperty介面屬性 public string Name { get { return "OrderTrace";} } } ...

最後是ContextAttribute

... [AttributeUsage(AttributeTargets.Class)] public class CallTraceAttribute : ContextAttribute { public CallTraceAttribute():base("CallTrace") { } //過載ContextAttribute方法,建立一個上下文環境屬性 public overr void GetPropertieorNewContext(IConstructionCallMessage ctorMsg) { ctorMsg.ContextProperties.Add(new CallTraceProperty()); } } }


為了看清楚呼叫Order物件的Submit方法如何被攔截,我們稍微修改一下Order類,同時把它設計為ContextBoundObject的派生類:

//Inventory.cs //Order.cs using System; namespace NiwalkerDemo { [CallTrace] public class Order : ContextBoundObject { ... public void Submit(string product, int quantity) { this.product=product; this.quantity=quantity; } ... } }


客戶端呼叫程式碼:

... public class AppMain { static void Main() { Order order1=new Order(100); order1.Submit("Item1",150); Order order2=new Order(101); order2.Submit("Item2",150); } } ...


執行結果表明了我們對Order的Sbumit成功地進行了攔截。需要說明的是,這裡的程式碼僅僅是作為對ContextAttribute應用的演示,它是粗線條的。在具體的實踐中,大家可以設計的更精妙。

後記:本來想對Attribute進行更多的介紹,發現要講的東西實在是太多了。請允許我在其他的專題中再來討論它們。十分感謝大家有耐心讀完這個系列。如果這裡介紹的內容在你的程式設計生涯有所啟迪的話,那麼就是我的莫大榮幸了。再一次謝謝大家。
(全文完)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-959293/,如需轉載,請註明出處,否則將追究法律責任。

相關文章