享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C#与NET实战 第二十二章 .Net Remoting

Posted on 2007-08-28 09:40  idior  阅读(1618)  评论(3编辑  收藏

22.15 .NET上下文

22.15.1 简介

我们已经看到AppDomain能够在运行期间实现类型、安全和异常层面的隔离。然而还存在着比AppDomain更加细粒度的用于存储.NET对象的实体。一个.NET AppDomain能够包含多个这种叫做.NET上下文的实体。在本章,只要不会与COM+上下文概念发生混淆,我们就把它们称作上下文,这两个概念是完全不同的(COM+上下文在8.8节介绍)。某些作者使用托管上下文(managed context)或者执行上下文(execution context)来指代.NET上下文,而非托管上下文则指代COM+上下文。所有.NET对象都存在于上下文中,每个AppDomain中至少存在一个上下文。这个上下文称为AppDomain的默认上下文,它在AppDomain创建的时候就创建了。图22-7总结了它们之间的关系。

图22-7 进程、AppDomain和上下文

上下文的概念使得拦截对象的调用成为可能。拦截调用意味着我们可以对每个输入或输出参数做一个或多个转换。这些转换是通过消息接收器来完成的。这样做主要是为了让客户端在调用对象的方法时不会察觉到调用被拦截了以及在方法处理的前后对参数做了转换。

22.15.2 上下文绑定和上下文灵活对象

根据上一节的内容,上下文可以看作AppDomain中一个包含对象和消息接收器的区域。对上下文里的对象的调用会转换成可以被消息接收器拦截和处理的消息。我们知道要把调用转换成消息,必须通过透明代理这个中介。而且,仅当对象是MarshalByRefObject的子类的实例并被其所在的AppDomain以外的实体调用时,CLR才会为它创建透明代理。这里,我们希望对所有调用使用消息接收器机制,即使是那些同一个AppDomain中的实体所执行的调用。这个时候我们就需要用到System.ContextBoundObject类了。继承自ContextBoundObject的类的实例同样仅能由透明代理访问。此时,甚至这个类的方法中所使用的this引用也是透明代理而不是对这个对象的直接引用。让ContextBoundObject类继承自MarshalByRefObject是合理的,因为它很好地强调了该类的特性——它告诉CLR这个类将会通过透明代理使用。

ContextBoundObject的子类的实例被视为是上下文绑定的。没有继承自ContextBoundObject的类的实例则被视为是上下文灵活的。上下文绑定的对象永远在其上下文中执行。只要不是远程对象,上下文灵活的对象总是在执行这个调用的上下文中执行。图22-8说明了以上内容。

图22-8 上下文绑定的对象与上下文灵活的对象

22.15.3 上下文attribute和上下文属性

下面我们将介绍用于在上下文层面注入消息接收器的技术。首先介绍上下文attribute和上下文属性的概念。

1.上下文attribute

上下文attribute是指应用在上下文绑定类上的.NET attribute。上下文attribute类实现了System. Runtime.Remoting.Contexts.IContextAttribute接口。上下文绑定的类可以应用多个上下文attribute。在这个类的对象创建期间,这个类的每个上下文attribute都要判断这个对象的创建者所在的上下文是否适用。下面这个方法执行了这个操作。

只要其中一个上下文attribute返回false,CLR就必须创建一个新的上下文来容纳这个新的对象。这样,每个上下文attribute都可以在这个新的上下文中注入一个或多个上下文属性。这些注入可以通过以下方法实现。

2.上下文属性

上下文属性是实现System.Runtime.Remoting.Contexts.IContextProperty接口的类的实例。每个上下文可以包含多个属性。上下文属性在上下文创建的时候通过上下文attribute注入。一旦每个上下文attribute都注入了它的属性,那么就会为每个属性调用下面的方法。此后就无法在这个上下文中注入另外的属性了。

然后,CLR通过调用下面的方法判断新的上下文能否满足每个属性。

每个上下文属性都有一个通过Name属性定义的名称,如下所示:

上下文中承载的对象的方法可以通过调用下面的方法访问上下文属性。

这一点很有意思,上下文中的对象通过它们所在的上下文的属性可以共享信息并访问服务。不过,上下文属性的主要作用并不在于此。上下文属性的主要作用在于向相关上下文中的消息接收器区域注入消息接收器。

对消息接收器区域的介绍是下一小节的主题。在此之前,让我们先通过示例演示上下文attribute和上下文属性的概念。对于那些读过信道那节的读者,上下文attribute的角色相当于在信道中注入提供程序的配置文件。同样地,上下文属性的角色相当于消息接收器提供程序。

3.使用上下文attribute和属性的示例

下面的程序定义了LogContextAttribute类和LogContextProperty类。应用了LogContext- Attribute的类的所有实例都将承载在具有LogContextProperty类型的属性的上下文中。于是,该实例就可以访问这个属性提供的服务了。这个服务允许通过调用LogContextProperty. Log(string)方法向文件写入一个字符串。文件名是LogContextAttribute的参数。这样,我们就可以让每个类都拥有一个配置文件。当应用了LogContextAttribute的类的新实例创建好后,boolLogContextAttribute. IsContextOK(Context)方法将判断调用构造函数的实体所在的上下文是否包含带有相同文件名的LogContextAttribute的实例。如果不是,则会创建一个新的上下文。LogContextAttribute.GetPro- pertiesForNewContext(IConstructionCallMessage ctor)方法创建一个LogContextProperty的实例。在这个方法返回时,CLR自动把新的属性注入新的上下文。下面是程序的代码。

例22-33

该程序输出:

obj1和obj3这两个对象位于同一个上下文中,因为obj3是在obj1所在的上下文中创建的。

22.15.4 消息接收器区域

消息接收器区域有以下四种:服务器区域、对象区域、信使区域和客户端区域。要理解区域概念,必须考虑上下文绑定的对象是否被位于另一个上下文中的实体调用。这个实体可以是一个静态方法或者另一个对象。在我们关于区域的讨论中,我们把这个实体所在的上下文称为调用方上下文,而把被调用对象所在的上下文称为目标上下文。目标上下文中的每个属性都可以在这些区域中注入消息接收器。

  • 注入服务器区域的消息接收器拦截所有从另一个上下文发往目标上下文中所有对象的调用消息。于是,每个目标上下文有一个服务器区域。
  • 注入对象区域的消息接收器拦截所有从另一个上下文发往目标对象中特定对象的调用消息。于是,上下文中每个对象会有一个对象上下文。
  • 注入信使区域的消息接收器拦截所有从另一个上下文发往目标对象中特定对象的调用消息。于是,上下文中的每个对象都有一个信使区域。信使区域和对象区域的不同点是信使区域位于调用方上下文而不是包含对象的目标上下文。我们使用信使区域把调用方上下文的信息传递给目标上下文的消息接收器。
  • 注入客户端区域的消息接收器拦截所有从目标上下文发往位于其他上下文的对象的调用消息。于是,每个目标上下文有一个客户端区域。

图22-9说明了区域的概念。目标上下文包含名为OBJ1和OBJ2的两个对象。我们选择在目标上下文中放置两个对象而不是一个是为了更好地说明对象区域和信使区域是在对象层面与消息的拦截关联起来的,而服务器区域和客户端区域则是在上下文层面与消息的拦截关联起来的。

我们在每个区域中放置了两个自定义消息接收器是为了更好地说明一个区域能包含零个、一个或多个消息接收器。具体地说,所有自定义消息接收器都通过目标上下文的属性注入区域,即使这个区域不属于目标上下文。因为上下文属性类是可以自定义的,所以我们可以选择哪个区域必须注入消息接收器。

你可能注意到,每个区域包含一个用于通知CLR退出区域的系统终结器接收器,不过不需要太在意它。

当调用方上下文和目标上下文处在同一个AppDomain中时,CLR会使用mscorlib.dll中CrossContextChannel内部类的实例作为信道。这个实例会使得当前线程的Context属性发生切换。图22-9展示了这些实例。

图22-9 上下文和区域

22.15.5 使用区域的示例

我们想通过例22-34的程序说明以下内容。

  • 自定义上下文attribute(CustomDisplayContextAttribute类)的代码在上下文中注入了自定义上下文属性(CustomDisplayContextProperty类)。这个上下文属性在目标上下文的对象区域、服务器区域和客户端区域以及调用方上下文的信使区域中注入了自定义消息接收器(CustomDisplayMessageSink类)。
  • 消息接收器的行为可以通过传递给上下文attribute的参数修改。这里,这个参数是一个布尔值,它指示消息接收器是否必须在控制台上输出某些东西。
  • 消息接收器通过上下文属性注入这四个区域。CustomDisplayContextAttribute上下文attribute确保为Foo类的每个实例创建上下文。我们首先创建Foo的实例,然后我们在这个实例上调用一个方法并穿越信使区域、服务器区域和对象区域的消息接收器。要穿越客户端区域的消息接收器,我们创建了Foo的第二个实例,然后让第一个实例调用它。于是,这里有了三个上下文:执行Main()方法的上下文(ContextID = 0)、包含Foo的第一个实例的上下文(ContextID = 1)和包含Foo的第二个实例的上下文(ContextID = 2)。
  • 上下文内部调用不会触发所有这些消息接收器的调用。这在Foo的第一个实例调用自身时可以看到。
  • 在区域(这里是客户端区域)中注入了多个消息接收器。
  • CLR在区域中注入消息接收器的时间。我们很清楚的看到这个时间依赖于区域的类型。

程序如下:

例22-34

执行程序后的输出如下所示。

22.15.6 调用上下文

可以在运行在调用方上下文中的消息接收器与运行在目标上下文中的消息接收器之间传递信息。我们使用这个技术主要是为了把调用方上下文的信息传递给目标上下文(例如调用方上下文支持某些属性)。我们把这项功能称为调用上下文。尽管它的名字中含有“上下文”这三个字,但是这项功能可以用在任何消息接收器中,包括那些不在上下文中的。

为此,必须首先定义一个实现System.Runtime.Remoting.Messaging.ILogicalThreadAffinative接口的类来描述要传递的信息。在运行在调用方上下文(在信使区域或者客户端区域里)中的消息接收器的代码中,我们必须把这个类的实例附加到表示调用的消息上。在运行在目标上下文(在对象区域或者服务器区域里)中的消息接收器的代码中,必须把这个类的实例从表示调用的消息中分离出来。这些操作是通过IMethodMessage.LogicalCallContext属性完成的。

下面的代码演示如何运用该技术。

例22-35

我们在前一节看到,在客户端区域和信使区域中注入消息接收器是发生在调用对象的构造函数之后。客户端区域和信使区域中的消息接收器无法把信息附加到表示构造函数调用的消息上,而服务器区域的消息接收器却可能依然试图从表示构造函数调用的消息中分离出这个信息。在这种情况下,我们可以在上下文attribute的GetPropertiesForNewContext()方法中附加这个信息。

例22-36

22.16 小结

22.16.1 激活对象的四种方式

我们已经看到,激活对象的方式有四种:WKO single-call模式、WKO singleton模式、CAO和对象发布。下面这张表总结了这些模式之间的主要差异。

22.16.2 截获消息

我们用了本章的一半专门来介绍如何拦截表示调用的消息以及为何这样做。图22-10简要总结了各种可能的拦截层面。

图22-10 拦截消息