在上一篇文章我們已經介紹到通過在配置文件中指定自定義的ChannelSinkProvider,我們可以在Pipeline中加入自己的ChannelSink,此時我們就可以加入自己的信息處理模塊,但是這裡我們所能操作的對象是已經經過格式化的消息(即數據流),我們看不到原始的消息對象,這也勢必影響了我們所能實現的擴展功能。而在上文的圖1中,我們看到除了ChannelSink可以擴展之外,我們還可以加入自定義的MessageSink,而它是位於格式器之前的,也就是說在MessageSink中我們可以直接操作尚未格式化的消息對象。此時,我們就獲得一個功能更強大的擴展點。直接操作消息對象,這意味著什麼呢?簡單來說,我們可以在這裡實現方法攔截,我們可以修改方法的參數、返回值,在調用方法前後加入自己的處理邏輯。是不是覺得聽上去很耳熟?沒錯,這就也正是AOP所要實現的一個目標。下面,在了解了整個Remoting的大背景以及ChannelSink的擴展機制後,我們將對MessageSink的擴展機制做進一步介紹。
在介紹前,我先提醒各位讀者注意以下幾點:
1. 確定你確實想深入了解Remoting的內部機制;
2. 確定你能很好的理解上一篇文章;
3. 如果說上一篇文章總結歸納的內容較多的話,在本文中出現的內容大多是筆者個人的探索,我想其他資料(包括英文資料)中都不曾介紹過這些內容,所以我不保證所有觀點的正確性,如果你覺得哪裡有誤,也歡迎你在評論中提出你的意見。
下面就讓我們開始品嘗大餐吧。
利用ChannelSinkProvider擴展MessageSink
MessageSink的擴展有兩種實現方法,讓我先從簡單的開始。在上一篇文章我們已經介紹到通過在配置文件中指定自定義的ChannelSinkProvider,我們可以在Pipeline中加入自己的ChannelSink。那麼有沒有一個類似於IClientChannelSinkProvider的IMessageSinkProvider呢?可惜答案是否定的。那麼我們能否通過IClientChannelSinkProvider插入一個MessageSink呢?插入之後它又能否發揮其功效呢?
首先我們先實現一個自定義的MessageSink。此時只需新建一個類,並實現IMessageSink接口中的SyncProcessMessage方法(為簡單起見我們只考慮同步調用模式),在方法中我們可以直接操作Message對象,比如我們可以向Message中加入額外的屬性,如下所示:
1: public class CustomMessageSink:IMessageSink
2: {
3: public IMessage SyncProcessMessage( IMessage msg )
4: {
5: // Add some custom data into msg.
6: ((IMethodMessage)msg).LogicalCallContext.SetData("MyName" , "idior" );
7: return m_NextSink.SyncProcessMessage( msg );
8: }
9: }
代碼 1
在上一篇文章的圖2中我們可以看到IClientChannelSinkProvider是通過下面這個方法創建ChannelSink。
1: public IClientChannelSink CreateSink(IChannelSender channel, string url,
2: object remoteChannelData) {...}
代碼 2
注意它的返回值是IClientChannelSink,而不是IMessageSink,這樣我們就無法將僅實現了IMessageSink接口的CustomMessageSink插入。為此,我們讓CustomMessageSink也實現IClientChannelSink接口,只不過在實現IClientChannelSink接口中的方法時,我們全部拋出異常,以表示這些方法不應該被調用到。這樣我們就可以瞞天過海般地利用ChannelSinkProvider創建出一個MessageSink。現在問題來了,這個MessageSink雖然創建出來了,但是它被插入Pipeline了嗎?其實,我們在上一篇文章中就漏過了一個問題——那些利用ChannelSinkProvider創建出來的ChannelSink是如何被插入到Pipeline中的,明白了它的原理,就自然解決了上面的問題。
Pipeline
Pipeline是何物?我們並沒有解釋清楚這個概念,是否存在一個對象它就叫Pipeline或者類似的名字?遺憾地告訴你,沒有!可以說這裡的Pipeline是一個抽象的概念,它表示了當我們調用一個遠程對象時從RealProxy到StackBuildSink之間所經過的一系列Sink的集合,但是並不存在一個單獨的鏈表把這些Sink全部鏈接起來。也就是說並不存在一個大的Sink鏈表,當你觸發遠程方法後,我們就依次從這個鏈表中取出一個個的Sink,大家挨個處理一下消息。不過在遠程對象的代理中倒是維護了一個由ChannelSink組成的鏈表。不過需要注意它並不代表整個Pipeline,而只能算是其中一部分,在後面我們會看到Pipeline中還包括了很多其他類型的Sink。這個鏈表保存在RealProxy的_identity對象中,鏈表是通過IClientChannelSink的Next屬性鏈接起來的,在_identity對象中保存鏈表的第一個元素,其他元素可以通過Next屬性獲得,如下圖所示:
下面我們來看看這個鏈表是如何得到的。每當我們通過TransparentProxy調用遠程方法時,如下圖所示,最終會調用到RemotingProxy中的InternalInvoke方法??????????$?????(,它將負責把各個ChannelSink創建出來並鏈接在一起。
1: internal virtual IMessage InternalInvoke(IMethodCallMessage reqMcmMsg,
2: bool useDispatchMessage, int callType)
3: {
4: //...
5: if (identity.ChannelSink == null)
6: {
7: IMessageSink envoySink = null;
8: IMessageSink channelSink = null;
9: if (!identity.ObjectRef.IsObjRefLite())
10: {
11: RemotingServices.CreateEnvoyAndChannelSinks(null,identity.ObjectRef,
12: out envoySink , out channelSink );
13: }
14: else
15: {
16: RemotingServices.CreateEnvoyAndChannelSinks(identity.ObjURI, null,
17: out envoySink , out channelSink );
18: }
19: RemotingServices.SetEnvoyAndChannelSinks(identity, envoySink,channelSink );
20: if (identity.ChannelSink == null)
21: {
22: throw new RemotingException("..."));
23: }
24: }
25: //...
26: }代碼 3
第一個判斷語句(Line 5)說明創建並鏈接ChannelSink的工作只發生在第一次調用,以後的每次調用將重復使用第一次的結果。第二個判斷語句(Line 9)暫且不管,我只需知道在下一步將創建出兩個Sink鏈,一個是EnvoySinl鏈,而另一個是ChannelSink鏈,前者我們也先不去管它(將在下部中介紹)而後者將通過out關鍵字傳給局部變量channelSink。其中CreateEnvoyAndChannelSinks方法最終會把ChannelSink鏈的創建任務交給Channel對象,至於Channel對象是如何配合ChannelSinkProvider工作的,我們在上一篇文章中已經介紹過了。
不知你有沒有注意到局部變量channelSink(Line 8)此時的類型是IMessageSink 而不是IClientChannelSink。到關鍵地方了,大家提起精神啊!明明我們創建的是ChannelSink鏈卻把頭元素的類型設為IMessageSink 。這是為什麼?大家知道在采用HttpChannel時,ChannelSink鏈的一個元素是什麼嗎?——SoapClientFormatterSink。你認為它應該是一個Message Sink還是Channel Sink?它是負責將消息對象格式為數據流的,操作對象是原始消息,自然應該是一個MessageSink。呵呵,原來搞了半天Remoting本身就有一個利用IClientChannelSinkProvider擴展MessageSink的例子(你可以在類庫中找到SoapClientFormatterSinkProvider)。如之前所述,SoapClientFormatterSink雖然是一個MessageSink,但是為了利用IClientChannelSinkProvider將其插入到Pipeline中,它也不得不實現IClientChannelSink接口,而且你可以看到它在實現IClientChannelSink接口中的方法時,全部拋出異常。如下所示:
1: public class SoapClientFormatterSink :IMessageSink, IClientChannelSink//...
2: {
3: //...
4:
5: //Implement method in IMessageSink
6: public IMessage SyncProcessMessage(IMessage msg)
7: {
8: IMethodCallMessage message1 = (IMethodCallMessage) msg;
9: try
10: {
11: ITransportHeaders headers1;
12: Stream stream1;
13: Stream stream2;
14: ITransportHeaders headers2;
15: this.SerializeMessage(message1, out headers1, out stream1);
16: this._nextSink.ProcessMessage(msg, headers1, stream1,
17: out headers2, out stream2);
18: if (headers2 == null)
19: {
20: throw new ArgumentNullException("returnHeaders");
21: }
22: return this.DeserializeMessage(message1, headers2, stream2);
23: }
24: catch (Exception exception1)
25: {
26: return new ReturnMessage(exception1, message1);
27: }
28: catch
29: {
30: return new ReturnMessage(new Exception("...")), message1);
31: }
32: }
33:
34: //Implement method in IClientChannelSink
35: public void ProcessMessage(...)
36: {
37: throw new NotSupportedException();
38: }
39:
40: //...
41: }代碼 4
然後在InternalInvoke方法中(代碼3),我們又通過SetEnvoyAndChannelSinks方法(Line19)把之前賦值的局部變量channelSink賦給RemotingProxy對象中identity對象的_channelSink變量,這樣一個個ChannelSink被鏈接在一起並能被代理對象所訪問。
現在我們可以確定通過IClientChannelSinkProvider完全可以向Pipeline中插入新的MessageSink。由於SoapClientFormatterSink的存在,我們也完全可以相信這個被插入到ChannelSink鏈中的MessageSink能正常的工作(即執行IMessageSink中的方法,而不是IClientChannelSink中的方法),不過為了讓大家更清楚Remoting的底層實現,我們還是想探究一下它是如何調用ChannelSink鏈中的一個個Sink來處理消息的。下圖就是調用一次遠程方法所產生的序列圖:
在上圖中,我們可以看到在InternalInvoke方法中將調用CallProcessMessage方法,它會把消息對象交給ChannelSink鏈中的第一個Sink處理。如下所示:
RemotingProxy.CallProcessMessage(identity.ChannelSink,reqMsg,...);
代碼 5
而我們在上圖中可以發現CallProcessMessage方法的第一個形參是IMessageSink類型的。也就是說通過IClientChannelSinkProvider方式插入到Pipeline中的第一個Sink,反倒是IMessageSink類型的,而不是IClientChannelSink。這也為插入到ChannelSink鏈中的MessageSink能正常工作掃清了障礙。正是因為這個原因SoapClientFormatterSink才能發揮其作用。
另外在利用IClientChannelSinkProvider插入MessageSink的時候,必須將它插入到FormatterSink的前面。因為只有在消息被Formmat之前,我們才能通過MessageSink對它進行處理,Format之後在Sink對消息的修改就無效了。這點在配置文件中體現為自定義的SinkProvider必須放在Formatter前面。不過這是針對客戶端而言,服務器端則恰恰與此相反。
<channel ref="http">
<clientProviders>
<provider type="CustomSinks.CustomSinkProvider,CustomSinks" />
<formatter ref="soap" />
</clientProviders>
</channel>
而在客戶端插入ChannelSink時,自定義的SinkProvider都是放在Formatter後面的。你可以在上一篇文章的圖2中發現這點。
總結
在本節中主要介紹了如何利用IClientChannelSinkProvider向Pipeline中加入MessageSink,從而在遠程方法調用中修改消息對象,實現功能更強大的擴展。並由此介紹了Remoting在實現此功能時,它的內部實現機制,有助於大家更深入地了解Remoting框架。
下一節將介紹當Client和Server對象處在同一個Appdomain時,如何攔截並修改消息,其中將涉及到更多類型的Sink。