通过在对象的拦截链中注入服务来解耦组件【翻译?】

  • SUMMARY

    类似于COM +组件服务提供的方式,.NET Framework使用contexts作为对象的执行范围并拦截调用。 与之不同的是,runtime允许开发人员参与到拦截链中,提供强大的服务来扩展已存在的组件服务。这样就从系统管道中解耦了业务逻辑并简化了长期维护。本文介绍了 .NET Framework底层拦截架构和消息处理,并解释了自定义上下文属性。

    组件技术最重要的一点是使用contexts来使组件技术更加容易。核心的设计模式是拦截:拦截来自client的对象、处理一些per-call、转发调用到对象、在返回client之前处理一些post-call。对象将自身的组件服务暴露给框架,并进行拦截,因此,框架能保证对象能获取到他们需要的runtime environment

    总的来说,.NET Framework能使开发人员提供自定义组件服务。对于软件工程和面向组件编程来说是一个重大的进步,因为开发人员可以微调.NET Framework支持的特定的应用程序和业务逻辑。自定义组件服务从client解耦了对象,因为他们不需要协调自定义服务的执行,从而可以专注于业务逻辑。自定义服务的例子有:应用程序日志和跟踪性能计数器,自定义线程管理,方法调用过滤、参数检查,安全访问检查,和事件订阅。

  • Contexts in .NET

    .NET Framework通过调用拦截来提供组件服务。为了拦截调用,一个代理被注入到client和对象之间。在相同应用程序域,默认情况下,client会直接引用同一个对象;不涉及代理。CLR可以随意将应用程序域细分成上下文。即使在同一个应用程序域中,当进行跨上下文进行调用时,client与对象之间总存在一个代理。如果你想利用interception-based服务,你需要从ContextBoundObject派生类,如下所示:

    public class SomeClass : ContextBoundObject
    {...}
    

    顾名思义,一个绑定上下文的对象总是在相同的上下文中执行,并且所有传递到上下文的调用都会被拦截。

    对象通过特殊的上下文特性表明他们需要的服务。.NET Framework提供一个SynchronizationAttribute特性,当一个上下文绑定对象使用SynchronizationAttribute特性时,CLR通过一个对象锁来确保在同一个时间只有一个线程可以访问该对象。如果对象正被另一个线程访问,访问会被组赛知道当前的访问结束。SynchronizationAttribute特性还提供了对象之间共享锁的方法以减少死锁的可能性。

  • Call Interception Architecture

    跨上下文拦截体系结构非常类似于一个用于执行远程调用跨应用程序域边界的方式。在 .NET Framework中代理有两种方式:一个透明的代理和一个真正的代理。透明代理公布了相同的公共入口点对象.当client调用透明代理时,它将堆栈帧转换为消息并将消息传递到真正的代理。消息是一个实现IMessage接口的对象。

    public interface IMessage 
    {    
        IDictionary Properties{ get; }
    }
    

    消息包含一组属性,如方法的名称和参数。对于跨应用程序域的调用,真正的代理需要序列化消息并将其传递给渠道。对于跨上下文的调用,真正的代理需要在转发调用到对象之前进行拦截步骤。事实证明,一个优雅的设计方案允许在两种情况下使用相同的真正的代理。真正的代理不知道格式器,渠道,或上下文拦截器。真正的代理将消息传递给消息接收器,消息接收器是一个继承了IMessageSink接口的对象:

    public interface IMessageSink 
    {
        IMessageSink NextSink{ get; }
        IMessageCtrl AsyncProcessMessage(IMessage msg,IMessageSink replySink);
        IMessage SyncProcessMessage(IMessage msg);
    }
    

    CLR将消息接收器汇集成消息链表。每一个消息接收器都知道链表中的下一个接收器,当执行完成后将消息传递下去。可以通过NextSink属性获取下一个消息接收器。

    通过在对象的拦截链中注入服务来解耦组件【翻译?】_第1张图片
    Alt text

    最后一个接收器被称为堆栈创建接收器,因为它将消息回传到堆栈帧上,然后调用对象上的方法。当调用回到堆栈创建接收器时,它构造了一个包含调用方法结果的消息,并将消息返回给调用方法的接收器。堆栈创建接收器执行一些post-call处理消息并返回到调等操作。消息链表中的第一个接收器将控制联通对象的结果信息返回给真实代理,真实代理将消息返回给透明代理,最后透明代理将消息待会client的调用堆栈。为了通过接收器传递消息,真实代理调用第一个接收器上SyncProcessMessage方法用以处理消息。接收器处理完消息后,继续调用下一个接收器的SyncProcessMessage方法,直到最后。在跨应用程序域调用中,client端的第一个接收器用于格式化消息。当SyncProcessMessage方法返回时,返回了对象的返回消息。IMessageSink接口同样提供了AsyncProcessMessage方法用于拦截异步调用。

  • Cross-context Sinks

    跨上下文调用不需要消息格式化。CLR会使用一个名为CrossContextChannel的内部管道(也是一个消息接收器),它帮助接收器弥补组件服务配置在client与独享之间的差异。

  • Message Sink Types

    调用拦截可能出现在两个地方。服务器端可以拦截一些进入上下文的调用以及做一些post-callpre-call处理,比如管理线程锁。客户端接收器拦截上下文发出的调用以及做一些post-callpre-call处理。比如说,Synchronization可以跟踪上下文以外的调用和释放锁以允许当一个外部调用正在进行时允许其他线程访问。这些都在客户端接收器上完成。客户端最后一个接收器和服务端第一个接收器都是CrossContextChannel类型。

    通过在对象的拦截链中注入服务来解耦组件【翻译?】_第2张图片
    Alt text
  • Custom Component Services

    自定义组件服务由自定义上下文特性服务。除非开发人员通过反射来寻找这些特性以解释他们的价值与行为,否则一般的自定义特性没有这样的功能。.NET Framework对于自定义特性的处理是不同的。

    不同于一般的自定义特性,.NET Framework能意识到使用在绑定上写文对象上的自定义上下文特性。它们必须继承自ContextAttribute类。当创建一个新的绑定上下文对象时,这个对象的元数据会被反射和放置到基于特性行为的合适的上下文上。自定义上下文特性会影响到对象激活和运行的上下文以及四种消息接收拦截器。

  • Custom Context Attribute

    每个上下文都有与之关联的一组属性。这些属性是一个上下文的组件服务支持。只要client的上下文有组件需要的服务,绑定上下文的对象就会与client共享它的上下文,换句话说,上下文具有不可或缺的属性。如果client的上下文不包含任何对象所需要的属性,CLR会创建一个新的上下文,然后将对象放进去。不管当前上下文如何,一个上下文属性可能会需要一个新的上下文。开发者使用上下文特性来指定需要的服务。上下文特性是判决client上下文是否充分的唯一指标。

    为了理解上下文特性对于一个上下文激活的影响,新建一个有颜色属性的上下文,颜色是一个ColorOption类型的枚举:

    public enum ColorOption{Red,Green,Blue};
    
    [Color(ColorOption.Blue)]
    public class SomeClass: ContextBoundObject
    {...}
    

    IsContextOK方法使用上下文特性检查创建client上下文提供的ctx参数,每一个上下文都有一个与之关联的上下文类型的对象,上下文对象能很方便的访问上下文属性。如果client上下文满足条件,运行时在创建的client上下文中激活对象,不需要进一步操作。如果IsContextOK方法返回false,创建一个新的上下文同时调用GetPropertiesForNewContext方法,允许上下文特性添加新的属性到新的上下文。IConstructionCallMessage类型的单一参数,通过ContextProperties属性提供了一个新的上下文属性的集合。

    由于一个绑定上下文的对象可能有多个上下文特性,有存在冲突的可能。为了解决这这种冲突,当所有属性都被添加到新的上下文中后,每个属性都会调用各自的IsNewContextOK方法,任意一个返回false,则新对象实例化失败并抛出一个异常。

    通过在对象的拦截链中注入服务来解耦组件【翻译?】_第3张图片
    Alt text
    
    [AttributeUsage(AttributeTargets.Class)]
    public class ColorAttribute : ContextAttribute
    {
        ColorOption m_Color;
    
        public ColorAttribute():this(ColorOption.Red)//Default color is red
        {}
    
        public ColorAttribute(ColorOption color):base("ColorAttribute")
        {
            m_Color = color;  
        }
        //Add a new color property to the new context 
        public override void GetPropertiesForNewContext(IConstructionCallMessage ctor)
        {
            ColorProperty colorProperty = new ColorProperty(m_Color);
            ctor.ContextProperties.Add(colorProperty);
        }
        //ctx is the creating client's context 
        public override bool IsContextOK(Context ctx,IConstructionCallMessage ctorMsg) 
        { 
            ColorProperty contextColorProperty = null;
            //Find out if the creating context has a color property. If not, reject it
            contextColorProperty = ctx.GetProperty("Color") as ColorProperty;
            if(contextColorProperty == null)
            {
                return false;
            }
            //It does have a color property. Verify color match 
            return (m_Color == contextColorProperty.Color);
        }
    }
    
    //The ColorProperty is added to the context properties collection by the ColorAttribute class
    public class ColorProperty : IContextProperty
    {
        protected ColorOption m_Color;
    
        public ColorProperty(ColorOption ContextColor) 
        {   
            Color = ContextColor;
        }
        public string Name
        {
            get 
            {
                return "Color";
            }
        }
        //IsNewContextOK called by the runtime in the new context
        public bool IsNewContextOK(Context ctx)
        {
            ColorProperty newContextColorProperty = null;
            //Find out if the new context has a color property. If not, reject it
            newContextColorProperty = ctx.GetProperty("Color") as ColorProperty;
            if(newContextColorProperty == null)
            {
                return false;
            }
            //It does have color property. Verify color match
            return (this.Color == newContextColorProperty.Color);
        }
    
        public void Freeze(Context ctx)
        {} 
        //Color needs to be public so that the attribute class can access it
        public ColorOption Color
        {
            get 
            { 
                return m_Color; 
            }
            set
            {
                m_Color = value;
            }
        }
    }
    
    
  • Providing Four Types of Sinks

    1. IContributeServerContextSink

      服务上下文接收器拦截所有进入上下文的调用,GetServerContextSink方法在* IContextProperty.IsNewContextOK*之后,创建对象之前被调用。所有可以根据上下文属性来提供接收器.可以拦截构造函数的调用。

    2. IContributeClientContextSink

      GetClientContextSink方法只有当对象在上文之外第一次调用时才会被调用,对象传递给接收器的消息属于目标对象而不是client

    3. IContributeEnvoySink

      此接收器会拦截所有从client到对象的调用,client访问其他对象不受影响。GetEnvoySink在一个新对象被创建后且在返回控制到client之前被调用。不可拦截构造函数的调用。

    4. IContributeObjectSink

      此接收器只能拦截对对象的调用,GetObjectSink方法在第一个调用方法被转发时调用。不可拦截构造函数的调用。

    [AttributeUsage(AttributeTargets.Class)]
    public class MyAOPAttribute : ContextAttribute, IContributeServerContextSink, IContributeObjectSink, IContributeClientContextSink, IContributeEnvoySink
    {
        public MyAOPAttribute() : base("MyAOP")
        {
            Console.WriteLine("MyAOP begin");
        }
        public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)
        {
            Console.WriteLine("GetObjectSink");
            return nextSink.AddLogSink();
        }
        public IMessageSink GetEnvoySink(MarshalByRefObject obj, IMessageSink nextSink)
        {
            Console.WriteLine("GetEnvoySink");
            return nextSink;
        }
        public IMessageSink GetClientContextSink(IMessageSink nextSink)
        {
            Console.WriteLine("GetClientContextSink");
            return nextSink;
        }
        public IMessageSink GetServerContextSink(IMessageSink nextSink)
        {
            Console.WriteLine("GetServerContextSink");
            //先添加后执行
            return nextSink;
        }
    }
    
    通过在对象的拦截链中注入服务来解耦组件【翻译?】_第4张图片
    Alt text
  • Processing Messages

    IMessage接口提供方法被拦截钱的所有消息集合,我们可以利用这些这些信息做一些消息处理逻辑。比如可以反射得到方法的特性来判断是否是事物操作等等。IMethodReturnMessage来源于IMethodMessage,它提供了额外的方法返回值的信息,输出参数的值,异常信息。如果抛出一个异常对象,堆栈创建者接收器默默地捕获它,并将它保存在返回的消息对象,这允许所有接收器检查异常对象的调用连。当控制返回给代理时,如果存在异常信息,代理重新抛出它在调用client的一边。

  • Conclusion

    一开始只是想深入了解一下 .NET Framework中代理的实现,在博客园中找了一下,基本上都只有例子,没有原理的讲解,MSDN中也只有寥寥几句话,最后不甘心终于在MSDN Magazine中找到一篇相关的文章。ORZ!!!

    文档下载

你可能感兴趣的:(通过在对象的拦截链中注入服务来解耦组件【翻译?】)