(承上節) .NET Framework攔截機制的設計中,在客戶端和對象之間,存在着多種消息接收器,這些消息接收器組成一個鏈表,客戶端的調用對象的過程以及調用返回實行攔截,你可以定製自己的消息接收器,把它們插入了到鏈表中,來完成你對一個調用的前處理和後處理。那麼調用攔截是如何構架或者說如何實現的呢?
在.NET中有兩種調用,一種是跨應用域(App Domain),一種是跨上下文環境(Context),兩種調用均通過中間的代理(proxy),代理被分爲兩個部分:透明代理和實際代理。透明代理暴露同對象一樣的公共入口點,當客戶調用透明代理的時候,透明代理把堆棧中的幀轉換爲消息(上一節提到的實現IMessage接口的對象),消息中包含了方法名稱和參數等屬性集,然後把消息傳遞給實際代理,接下去分兩種情況:在跨應用域的情況下,實際代理使用一個格式化器對消息進行序列化,然後放入遠程通道中;在跨上下文環境的情況下,實際代理不必知道格式化器、通道和Context攔截器,它只需要在向前傳遞消息之前對調用實行攔截,然後它把消息傳遞給一個消息接收器(實現IMessageSink的對象),每一個接收器都知道自己的下一個接收器,當它們對消息進行處理之後(前處理),都將消息傳遞給下一個接收器,一直到鏈表的最後一個接收器,最後一個接收器被稱爲堆棧創建器,它把消息還原爲堆棧幀,然後調用對象,當調用方法結果返回的時候,堆棧創建器把結果轉換爲消息,傳回給調用它的消息接收器,於是消息沿着原來的鏈表往回傳,每個鏈表上的消息接收器在回傳消息之前都對消息進行後處理。一直到鏈表的第一個接收器,第一個接收器把消息傳回給實際代理,實際代理把消息傳遞給透明代理,後者把消息放回到客戶端的堆棧中。從上面的描述我們看到穿越Context的消息不需要格式化,CLR使用一個內部的通道,叫做CrossContextChannel,這個對象也是一種消息接收器。
有幾種消息接收器的類型,一個調用攔截可以在服務器端進行也可以在客戶端進行,服務器端接收器攔截所有對服務器上下文環境中對象的調用,同時作一些前處理和後處理。客戶端的接收器攔截所有外出客戶端上下文環境的調用,同時也做一些前處理和後處理。服務器負責服務器端接收器的安裝,攔截對服務器端上下文環境訪問的接收器稱爲服務器上下文環境接收器,那些攔截調用實際對象的接收器是對象接收器。通過客戶安裝的客戶端接收器稱爲客戶端上下文環境接受器,通過對象安裝的客戶端接收器則稱爲特使(Envoy)接收器,特使接收器僅攔截那些和它相關的對象。客戶端的最後一個接收器和服務器端的第一個接收器是CrossContextChannel類型的實例。不同類型的接收器組成不同的段,每個段的端點都裝上稱爲終結器的接收器,終結器起着把本段的消息傳給下一個段的作用。在服務器上下文環境段的最後一個終結器是ServerContextTerminatorSink。如果你在終結器調用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");
SendEmail();
}
}
}
//後處理方法,用於記錄訂單提交信息,同樣可以將記錄作爲一個接收器
//我們在這裏處理,僅僅是爲了演示
private void Postprocess(IMessage msg,IMessage retMsg)
{
IMethodCallMessage call=msg as IMethodCallMessage;
if(call==null)
return;
Console.WriteLine("Log order information");
}
private void SendEmail()
{
Console.WriteLine("Send email to manager");
}
}
...
接下來我們定義上下文環境的屬性,上下文環境屬性必須根據你要創建的接收器類型來實現相應的接口,比如:如果創建的是服務器上下文環境接收器,那麼必須實現IContributeServerContextSink接口。
...
public class CallTraceProperty : IContextProperty, IContributeObjectSink
{
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 override void GetPropertiesForNewContext(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進行更多的介紹,發現要講的東西實在是太多了。請允許我在其他的專題中再來討論它們。十分感謝大家有耐心讀完這個系列。如果這裏介紹的內容在你的編程生涯有所啓迪的話,那麼就是我的莫大榮幸了。再一次謝謝大家。(全文完)
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/okvee/archive/2008/07/04/2610392.aspx