zoukankan      html  css  js  c++  java
  • 用Delphi实现动态代理(1):概述 转

    转 http://blog.csdn.net/raptor/article/details/304949

    用Delphi实现动态代理(1):概述

    [Mental Studio]猛禽[Blog]

    一、问题

    所谓动态代理(Dynamic Proxy),要先从GoF的Proxy模式说起。

    假设有一个IFoo接口:

    {$M+}
    IFoo = Interface( IInterface )
    ['{3A85E46D-F3D4-4D9C-A06C-4E7C1BAC9361}']
      Function doSth( dummy : Integer ) : String; StdCall;
      Procedure bar; StdCall;
    End;
    {$M-}

    接口提供者对其作了实现,并提供了一个工厂方法(Factory Method)来向用户提供了实例的创建,如下:

    TFooImpl = class(TInterfacedObject, IFoo)
      Protected
       Function doSth( dummy : Integer ) : String; StdCall;
       Procedure bar; StdCall;
    end;

    (*
    TFooImpl的实现代码,略
    *)

    // 创建实例的工厂方法
    Function GetFooObject( ) : IFoo;
    Begin
    Result := TFooImpl.Create As IFoo;
    End;

    作为这个接口的用户,只有IFoo接口的定义,并且可以一个创建的实现IFoo接口的实例,但没有实现类TFooImpl的定义和实现代码。如果现在用户需要为IFoo.doSth增加事务功能(假设doSth被实现为对数据库作更新操作),要怎么办?
     

    二、静态代理解决方案

    GoF的Proxy模式就是解决方案之一:

    如图所示,首先要定义一个新的IFoo接口实现--TStaticProxy。其中用了一个属性FImpl记录了TFooImpl的实例。然后在 TStaticProxy中实现doSth和bar,并且将不需要变更的bar函数直接委托给FImpl处理,而在doSth的实现里加入事务处理即可。 TStaticProxy的代码大致如下:

    TStaticProxy = class(TInterfacedObject, IFoo)
    Private
    FImpl : IFoo;
    Protected
    Function doSth( dummy : Integer ) : String; StdCall;
    Procedure bar; StdCall;
    public
    constructor Create( aImpl : IFoo );
    end;

    { TStaticProxy }

    constructor TStaticProxy.Create(aImpl: IFoo);
    begin
    FImpl := aImpl;
    end;

    // 新的doSth,加入了数据库事务处理
    function TStaticProxy.doSth(dummy: Integer): String;
    begin
    DBConn.StartTransaction;
    Try
    FImpl.doSth( dummy );
    DBConn.Commit;
    Except
    DBConn.Rollback;
    End;
    end;

    procedure TStaticProxy.bar;
    begin
    FImpl.bar;
    end;

    // 新的工厂方法
    Function NewGetFooObject( ) : IFoo;
    Begin
    Result := TStaticProxy.Create( GetFooObject( ) ) As IFoo;
    End;

    现在,用户只需要用新的工厂方法NewGetFooObject来代替原来的GetFooObject即可,新的工厂方法返回的实例就已经具备了为doSth增加事务处理的能力。

    可见,我们通过了一个Proxy类代理了所有对IFoo接口的操作,相当于在Client与TFooImpl之间插入了额外的处理代码,在某种程度上,这就是AOP所谓的“横切”。
     

    三、静态代理的问题

    但上面这种静态代理解决方案还是很麻烦:

    • 首先,如果IFoo的成员函数很多的话,必须要一一为它们加上代理实现;
    • 其次,如果在应用中有很多接口需要代理时,就必须一一为它们写这样的专用代理类;
    • 第三,需要变更代理功能时,需要修改所有的代理类
    • ……

    当然,这些问题也不是非要“动态代理”不可。

    比如第一点。如果用户拥用TFooImpl的代码,就可以直接从TFooImpl派生一个TNewFooImpl,然后在其中Override一下TFooImpl中的doSth即可。最后修改工厂方法,改为创建并返回TNewFooImpl的实例。如下图所示:

    问题就在于必须拥用TFooImpl的代码才行,而这在很多时候是做不到的--除非不是用DELPHI,而是如 Python一类的动态语言。在一些比如组件容器,比如远程接口调用,还有像“虚代理”(就是当创建FImpl代价很高时,就在创建时只创建代理类,然后 在真正需要时才创建FImpl的实例)这样的应用,通常都是只能得到接口定义和相应的实例。

    正因为没有TFooImpl的代码,所以我们不得不用比较麻烦一些的静态代理。可以注意一下前面的代码,其中并没有用到TFooImpl类。

    至于第二第三两个问题,如果对于像C++那样支持GP(泛型编程)的语言,则可以通过template来实现。可惜在Delphi.net以前,并不支持这个Feature。

    再说对于像组件容器或是通用远程接口调用这样的应用,被代理的接口要到运行时才可以确定的情况下,静态代理一点用也没有--因为它必须实现所要代理 的接口,如上面那个TStaticProxy就实现了IFoo接口。这一点GP也是无能为力的,因为模板毕竟只是一种编译期动态化的特性。
     

    四、动态代理

    所以我们需要“动态代理”。这个概念是JAVA在JDK1.3中提出的,就是在java.lang.reflect中的那个proxy[1]。因为 DELPHI是所有静态编译语言中,动态性最强的,所以也是可以实现这样的功能,我已经用DELPHI完成了一个与JAVA类似的动态代理实现[2]。

    一个典型的动态代理应用如下:

    //  因为TMInterfaceInvoker需要类实例,所以原来这个工厂方法需要改成返回对象
    Function GetFooObject : TObject;
    Begin
    Result := TFooImpl.Create( );
    End;

    TFooInvHandler = class( TInterfacedObject, IMInvocationHandler )
    private
    FImpl : IFoo;
    FInvoker : IMMethodInterceptor;
    Protected
    Procedure Invoke( const aProxy : TMDynamicProxy;
    const aContext: TMMethodInvocation ); StdCall;
    Public
    Constructor Create;
    end;

    { TFooInvHandler }

    constructor TFooInvHandler.Create;
    Var
    tmp : TObject;
    begin
    tmp := GetFooObject( ); // tmp是实例,不会影响引用计数
    FInvoker := TMInterfaceInvoker.Create( tmp );
    Supports( tmp, IFoo, FImpl ); // 将对象转为接口实例,
    // 主要是为了将引用计数设置为1,以免对象被无意中释放
    end;

    Procedure TFooInvHandler.Invoke( const aProxy : TMDynamicProxy;
    const aContext: TMMethodInvocation );
    begin
    If ( aContext.MethMD.Name = 'doSth' ) Then
    Begin
    DBConn.StartTransaction;
    Try
    FInvoker.Invoke( aContext );
    DBConn.Commit;
    Except
    DBConn.Rollback;
    End;
    End
    Else
    FInvoker.Invoke( aContext );
    end;

    // 新的工厂方法
    Function NewGetFooObject( ) : IFoo;
    Begin
    Result := TMDynamicProxy.Create( TypeInfo( IFoo ), TFooInvHandler.Create( ) ) As IFoo;
    End;

    上面代码实现的功能与那个静态代理的例子是一样的。

    首先看一下新的工厂方法。其实现与静态代理是比较相似的,重要的不同点就在于:这个TMDynamicProxy是一个通用的代理类,不像 TStaticProxy,必须根据要实现的接口来定制。而TMDynamicProxy实现对接口调用的动态代理功能和附加功能的切入是通过两个参数实 现,根据运行时传入参数的不同,它就可以“动态”地实现对不同接口的代理,以及不同附加功能的切入。

    所以它叫做“动态代理”。

    不过因为DELPHI毕竟还是一种编译型的语言,所以对于这个动态代理的实现除了大量使用DELPHI本身强大的RTTI功能以外,还用到了像 Thunk这样的技术,在某种程度上侵入了编译器的“势力范围”,但这也是不得已的。幸好这些仅存在于动态代理本身的实现中,对于使用动态代理的应用,基 本上可以做到跟JAVA中差不多。

    TMDynamicProxy的构造参数中,TypeInfo( IFoo )就是传入的接口类型信息,用于实现动态接口实现。而TFooInvHandler的实例则是切入的附加功能代码。

    所以接下来要关注的就是这个TFooInvHandler的实现。TFooInvHander是一个实现了IMInvocationHandler的类。而IMInvocationHandler的定义如下:

      IMInvocationHandler = Interface
    Procedure Invoke( const aProxy : TMDynamicProxy;
    const aContext : TMMethodInvocation ); StdCall;
    End;

    TMMethodInvocation = class
    public
    Property IID : TGUID;
    Property CallID : Integer;
    Property MethMD : TIntfMethEntry;
    Property Params[aIndex : Integer] : Variant;
    Property RetVal : Variant;
    End;

    这个接口只定义了一个Invoke方法,TMDynamicProxy将所有对被代理接口的方法调用都代理到此方法上。类型为 TMMethodInvocation的参数aContext记录了方法调用的上下文,包括接口ID、方法ID、Method Meta Data(方法的RTTI元数据)、参数列表、返回值等。

    在例子中实现的TFooInvHandler的Invoke方法实现中,判断被调用的方法名是否是“doSth”,如果是则插入事务处理,否则将 Invoke委托给一个IMMethodInterceptor接口实例处理。我设计此接口是准备用于实现AOP中的动态拦截器,但在此例中,这个实例对 应的是一个TMInterfaceInvoke类对象。这个类也是一个像TMDynamicProxy一样的通用类,用于实现将Invoke调用 Dispatch到具体实现类对象的相应方法调用上。因为它是通过TObject的一些RTTI特性实现,这些功能无法通过接口实例得到,所以需要将原来 的工厂方法返回的接口对象改为一般类对象,返回TObject类型并不失一般性(仍然是没有TFooImpl的实现代码)。

    注意,在TFooInvHandler的实现中,只判断了方法名,没有判断接口ID。这是因为在这个例子中,它只处理IFoo接口的调用,所以不需要。但如果是AOP应用,一个拦截器通常可以用于多个接口,这里就必须要判断IID了。

    整个动态代理应用的结构大致如下图:

    有了这样一个动态代理,除了可以像这个例子一样切入事务处理以外,还可以很方便地切入如安全性检查,LOG等。这样的话,用DELPHI来实现AOP也不成问题了。
  • 相关阅读:
    C#下解决DrawImage画出来的Image变大了的问题
    WPF的TextBox产生内存泄露的情况
    【技术积累】【C#】创建符号链接
    Wix学习整理(7)——在开始菜单中为HelloWorld添加卸载快捷方式
    Wix学习整理(5)——安装时填写注册表
    Wix学习整理(4)——关于WiX文件格式和案例HelloWorld的分析
    【技术积累】【C#】生成字符串的MD5值
    Wix学习整理(6)——安装快捷方式
    【小技巧积累】设置ListView控件的Item不在Tab键导航序列中
    修改Windows的本地hosts文件以访问facebook
  • 原文地址:https://www.cnblogs.com/luckForever/p/7254946.html
Copyright © 2011-2022 走看看