分享
 
 
 

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

王朝delphi·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

用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也不成问题了。

(未完待续)

参考文献:

[1]熊节《动态代理的前世今生》(《程序员》2005年第1期)

[2]我用DELPHI实现的动态代理代码可以在这里下载,还在改进中,仅供参考。

[Mental Studio]猛禽 Feb.03-05, Feb.27

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有