閱讀389 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF服務端運行時架構體係詳解[續篇]

終結點分發器在自己的運行時中對請求消息的處理最終肯定體現在相應操作的執行。如果從服務描述的角度來看,操作是一個OperationDescription對象。而服務端分發運行時中的操作則代表的是一個DispatchOperation對象。作為服務描述的一部分,服務所有終結點的所有操作描述(OperationDescription)在ServiceHost創建過程中被創建。而當ServiceHost被正常開始時,這些操作描述最終轉換成分發操作(DispatchOperation)。而DispatchRuntime的Operations屬性代表了對應終結點的所有分發操作。

目錄:
一、序列化與反序列
二、調用上下文初始化
三、參數的檢驗
四、服務實例的釋放
五、事務
六、操作的執行
七、參數和返回值的釋放
八、身份模擬
總結

接下來,我們同樣從可擴展的角度來分析DispatchOperation,下麵的代碼片斷列出了所有可供擴展的屬性。

   1: public sealed class DispatchOperation
   2: {
   3:     //序列化與反序列化
   4:     public bool DeserializeRequest { get; set; }
   5:     public bool SerializeReply { get; set; }
   6:     public SynchronizedCollection<FaultContractInfo> FaultContractInfos { get; }
   7:     public IDispatchMessageFormatter Formatter { get; set; }
   8:  
   9:     //執行上下文初始化
  10:     public SynchronizedCollection<ICallContextInitializer> CallContextInitializers { get; }
  11:  
  12:     //參數檢驗
  13:     public SynchronizedCollection<IParameterInspector> ParameterInspectors { get; }
  14:  
  15:     //服務實例釋放
  16:     public bool ReleaseInstanceAfterCall { get; set; }
  17:     public bool ReleaseInstanceBeforeCall { get; set; }
  18:  
  19:     //事務
  20:     public bool TransactionAutoComplete { get; set; }
  21:     public bool TransactionRequired { get; set; }
  22:  
  23:     //操作執行
  24:     public IOperationInvoker Invoker { get; set; }
  25:  
  26:     //參數/返回值的釋放
  27:     public bool AutoDisposeParameters { get; set; }
  28:  
  29:     //身份模擬
  30:     public ImpersonationOption Impersonation { get; set; }  
  31: }

我們所示的服務操作的執行最終體現在執行服務實例的某個相應的操作方法。而調用方法需要傳入參數,而參數是一個個實實在在的基於某種類型的對象。但是在這之前,所有的服務調用信息被封裝在消息中(對應於Message對象)。那麼,在真正執行方法調用之前首要的任務就是從請求消息中提取相應的信息並將其反序列化成方法的輸入參數。在另一方麵,當操作方法被正確執行後,執行的結果通過方法的返回值(或者ref/out參數)來體現。如果要將執行結果正確地回複給客戶端,需要將它們進行序列化成消息。

如果你閱讀了《WCF技術剖析(卷1)》第5章《序列化與數據契約》,你應該很清楚WCF通過一個被稱為消息格式化器(MessageFormatter)組件來完成序列化和反序列化工作。對於服務端來說,這個消息格式化器被稱為分發消息格式化器(DispatchMessageFormatter),它實現了一個具有如下定義的System.ServiceModel.Dispatcher.IDispatchMessageFormatter接口。兩個方法分別用於對請求消息的反序列化和對回複消息的序列化。

   1: public interface IDispatchMessageFormatter
   2: {
   3:     void DeserializeRequest(Message message, object[] parameters);
   4:     Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result);
   5: }

DispatchOperation的Formatter屬性決定以最終采用的消息格式化器。在默認的情況下,WCF利用DataContractSerializer作為序列化器的消息格式化器。如果我們希望采用傳統的XML序列化方式,我們也可以使用基於XmlSerializer作為序列化器的消息格式化器。兩種不同消息格式化器的選擇可以通過如下兩個對應的操作行為來實現:DataContractSerializerOperationBehaviorXmlSerializerOperationBehavior

此外,與序列化相關的還具有兩個布爾類型的屬性DeserializeRequest和SerializeReply。從語義上我們都知道,它們分別表示是否需要進行請求消息的反序列化和回複消息的序列化。隻所有要為DispatchOperation定義如此兩個屬性,原因在於消息的序列化反序列化並不是在任何情況下都是需要的。比如,如果操作方法具有一個唯一的類型為Message的參數,那麼對請求消息的反序列化是不需要的。同理,如果操作方法的返回值(並且沒有ref/out參數)類型為Message,那麼就不需要進行對回複消息的序列化。

上麵介紹的都是基於正常服務調用情況下的序列化和反序列化。如果異常出現,我們可以針對定義在操作上的錯誤契約(Fault Contract)拋出FaultException<TDetail>異常。為了順利完成針對該異常消息信息(TDetail類型對象),需要預先確定必要的錯誤契約相關的信息。這些輔助性信息被風轉在一個FaultContractInfo對象中,而DispatchOperation的FaultContractInfos表示與該操作所有錯誤契約相關的FaultContractInfo集合。

每個DispatchOperation都有一個以屬性CallContextInitializers表示的調用上下文初始化器(CallContextInitializer)列表。每個CallContextInitializer都實現了具有如下定義的接口ICallContextInitializer

   1: public interface ICallContextInitializer
   2: {
   3:     void AfterInvoke(object correlationState);
   4:     object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message);
   5: }

當目標操作方法執行前後,列表中每個CallContextInitializer的BeforeInvoke和AfterInvoke方法分別被調用,用以實現針對操作方法執行環境的初始化和清理工作。其中BeforeInvoke的返回值會被傳到AfterInvoke方法中作為輸入參數(correlationState)。

舉個例子,在《WCF技術剖析(卷1)》的第10章,我分別采用自定義ClientMessageInspector和CallContextInitializer實現了上下文信息從客戶端到服務端的自動傳播。其中ClientMessageInspector用於上下文信息的發送,而CallContextInitializer用於上下文信息的接收和設置。在接下來的部分,我們還將演示如何利用這兩個組件(ClientMessageInspector和CallContextInitializer)實現另一個應用場景:讓服務操作執行線程的語言文化與客戶但保持一致。

參數的檢驗涉及到另一個重要的組件,即參數檢驗器(ParameterInspector),它實現了一個具有如下定義的接口IParameterInspector。每個DispatchOperation具有一個ParameterInspector列表,通過屬性ParameterInspectors表示。

   1: public interface IParameterInspector
   2: {
   3:     void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState);
   4:     object BeforeCall(string operationName, object[] inputs);
   5: }

在當MessageFormatter將請求消息反序列化為針對某個操作的參數,到操作方法被執行這段時間內,列表中的每個ParameterInspector的BeforeCall方法會被執行。操作名稱和分序列化後的參數會被傳入這個方法。另一方麵,從操作方法執行成功,到MessageFormatter將以返回值(或者ref/out參數)形式體現的執行結果序列化成回複消息的這段時間內,列表中的每個ParameterInspector的AfterCall會被調用。而四個輸入參數分別表示操作方法、輸出參數數組、返回值和BeforeCall方法的返回值。

我們通常通過自定義ParameterInspector的方式實現操作執行前對輸入參數的檢驗,以及操作執行後對返回值/輸出參數的檢驗。舉個例子,微軟的企業庫(Enterprise Library)具有驗證應用程序塊(Validation Application Block)。這是一個非常不錯的用於數據實體驗證的框架,它允許你可以單獨定義針對某個實體類型的驗證策略。它提供了於WCF的集成,使我們可以將這些獨立的驗證策略通過聲明或者配置的方式應用到相應的操作上,最終實現自動的參數驗證。而最終完成驗證的就是自定義的ParameterInspector。

包含有服務實例釋放的時機涉及到DispatchOperation的兩個屬性ReleaseInstanceBeforeCall和ReleaseInstanceAfterCall。前者表明在服務操作之前釋放現有的實例並創建新的實例。後者這表示服務實例會在服務操作執行後被釋放。服務實例的釋放通過調用InstanceContext的ReleaseServiceInstance方法,而該方法一般來說會在最終調用之前介紹的InstanceProvider的ReleaseInstance方法。

   1: public sealed class InstanceContext : CommunicationObject, IExtensibleObject<InstanceContext>
   2: {
   3:     //其他成員
   4:     public void ReleaseServiceInstance();
   5: }
   6: public interface IInstanceProvider
   7: {
   8:     //其他成員
   9:     void ReleaseInstance(InstanceContext instanceContext, object instance);
  10: }

這兩個值可以通過操作行為OperationBehaviorAttribute來控製。OperationBehaviorAttribute具有一個類型為ReleaseInstanceMode枚舉的ReleaseInstanceMode屬性。我們可以將該特性應用到相應的操作方法上並指定相應的實例釋放模式來控製服務實例的回收是在操作調用前還是調用後執行。

   1: [AttributeUsage(AttributeTargets.Method)]
   2: public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior
   3: {  
   4:     //其他成員
   5:     public ReleaseInstanceMode ReleaseInstanceMode { get; set; }
   6: }
   7: public enum ReleaseInstanceMode
   8: {
   9:     None,
  10:     BeforeCall,
  11:     AfterCall,
  12:     BeforeAndAfterCall
  13: }

DispatchOperation的兩個布爾類型的屬性TransactionRequired和TransactionAutoComplete是與事務相關。前者表示當前的操作是否應該在事務中執行,後者表示當操作執行之後是否自動提交事務。我們可以通過操作行為OperationBehaviorAttribute的TransactionScopeRequired和TransactionAutoComplete屬性來控製它們。

   1: [AttributeUsage(AttributeTargets.Method)]
   2: public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior
   3: {
   4:     //其他成員
   5:     public bool TransactionAutoComplete { get; set; }
   6:     public bool TransactionScopeRequired { get; set; }
   7: }

操作的執行最終落在了一個被稱為操作執行器(OperationInvoker)的組件上。OperationInvoker實現了具有如下定義的IOperationInvoker接口。操作具有兩種不同的執行方法,即同步和異步。通過操作執行實現在Invoke方法中,而InvokeBegin/InvokeEnd則用於實現異步執行。方法中的輸入參數instance和inputs分別表示用於通過InstanceProvider提供的服務實例,以及通過MessageFormatter對請求消息進行反序列化生成的輸入參數。而Invoke和InvokeEnd的返回值和輸出參數(outputs)表示操作執行後得到的結果。

   1: public interface IOperationInvoker
   2: {
   3:     object[] AllocateInputs();
   4:     object Invoke(object instance, object[] inputs, out object[] outputs);
   5:     IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state);
   6:     object InvokeEnd(object instance, out object[] outputs, IAsyncResult result);
   7:     bool IsSynchronous { get; }
   8: }

至於如何決定應該采用同步還是異步的操作執行方式,取決於隻讀屬性IsSynchronous。而AllocateInputs方法用於返回一個元素個數為當前操作參數數量相同的數組。當MessageFormatter完成了對請求消息的反序列化後會用生成的參數填充該數組。被填充的數組最終作為輸入參數傳入Invoke或者InvokeBegin方法中。

WCF提供兩個內部(Internal)的OperationInvoker分別實現了操作的同步和異步實現,它們分別是SyncMethodInvoker和AsyncMethodInvoker。它們的實現原理其實很簡單,就是采用反射的方式調用相應的操作方法。具體來說,SyncMethodInvoker和AsyncMethodInvoker通過調用操作描述(OperationDescription)的SyncMethod和BeginMethod/EndMethod實現了對操作的同步和異步調用。

   1: public class OperationDescription
   2: {
   3:     //其他成員
   4:     public MethodInfo BeginMethod { get; set; }
   5:     public MethodInfo EndMethod { get; set; }
   6:     public MethodInfo SyncMethod { get; set; }
   7: }

當服務操作成功執行,並且執行的結果被序列化到回複消息中,無論是作為參數的對象還是作為返回值的對象都變成了“垃圾對象”。在正常的情況下,它們最終會被垃圾回收。但是,如果這些對象引用一些需要釋放的資源,就有可能造成內存泄露。

我們應該很清楚,我們在設計這種類型的時候,一般會實現IDisposable接口,並將資源釋放操作實現在Dispose方法中。而DispatchOperation的AutoDisposeParameters屬性決定了對於實現了IDisposable接口的類型的參數和返回值,是否需要最終調用它們的Dispose方法。

我們可以通過操作行為OperationBehaviorAttribute的同名屬性控製DispatchOperation的AutoDisposeParameters屬性值。在默認的情況下,DispatchOperation的AutoDisposeParameters屬性為True。如果你希望直接避免參數和返回值的釋放操作,你可以通過該特性將屬性設置為False。

   1: [AttributeUsage(AttributeTargets.Method)]
   2: public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior
   3: {
   4:     //其他成員
   5:     public bool AutoDisposeParameters { get; set; }
   6: }

最後一個類型為ImpersonationOption類型的屬性Impersonation在《模擬在WCF中的應用》已經詳細介紹過了,用以表示是否在模擬客戶端身份上下文中執行服務操作。它對應操作行為OperationBehaviorAttribute的同名屬性。

   1: [AttributeUsage(AttributeTargets.Method)]
   2: public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior
   3: {
   4:     //其他成員
   5:     public ImpersonationOption Impersonation { get; set; }
   6: }

對於一個在分發運行時表示某個具體的服務操作的DispatchOperation對象來說,它的可擴展的核心組件包括CallContextInitializer、ParameterInspector、DispatchMessageFormatter和OperationInvoker。它們在DispatchOperation如下圖所示。

clip_image002

WCF服務端運行時架構體係詳解[上篇]
WCF服務端運行時架構體係詳解[中篇]
WCF服務端運行時架構體係詳解[下篇]
WCF服務端運行時架構體係詳解[續篇]


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 16:04:44

  上一篇:go  WCF服務端運行時架構體係詳解[下篇]
  下一篇:go  通過WCF擴展實現消息壓縮