• 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服务端运行时架构体系详解[续篇]

  • 相关阅读:
    Ajax(三)
    Ajax(二)
    Django(四)
    Ajax(一)
    Django(三)
    Django(二)
    Django(一)
    Http协议
    Bootstrap
    python 绑定方法
  • 原文地址:https://www.cnblogs.com/artech/p/diapatch04.html
Copyright © 2020-2023  润新知