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

  • 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属性获取下一个消息接收器。

    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类型。

    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,则新对象实例化失败并抛出一个异常。

    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;
        }
    }
    
    Alt text
  • Processing Messages

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

  • Conclusion

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

    <a href="http://oj3hjjxg2.bkt.clouddn.com/MSDNMagazineMarch2003en-us.chm">文档下载</a>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707
  • 夜雨熙熙阅读 147评论 0 0
  • 听惯了你的歌, 却又觉得它是那样凄凉与伤感 。 看惯了你的脸, 却又觉得它是那样陌生与遥远, 牵惯了你的手, 却又...
    爱我家的么么哒阅读 305评论 1 10
  • 很久没进书店的我,注意到柜台上从原来摆满诗词歌赋的文集被励志成功学的书籍占领。有点诧异,什么时候这类书籍成了爆款,...
    安清儿阅读 249评论 0 2