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

用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 MetaData(方法的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

从抢钱说起

昨天的《道德观察》说了一件事:成都金牛区某商业区发生一起抢劫案,劫匪被众人擒获,而被抢的27万元现金掉了一地,在场的人们自觉地围成一个圈保护散落在街头的钱,等着失主用了半个小时时间把钱全部捡回来,居然一张都没有少。

这很难得。但,这也是一种“意外”。

其实这种街上散钱的事以前也有报道,但几乎无一例外是遭遇哄抢。不要说是钱了,别的物资也一样。据说某地发生过一起运输大豆的汽车发生交通事故,倾覆在路边,等到有关部门赶到时,车上的大豆已经被人哄抢一空。

假设如果钱刚掉时,那些人不是帮着保护,而是往自己口袋里装钱,那么结果肯定是演变成一场哄抢,结果肯定是那个失主损失了。

但当作保护工作的人达到一定的数量,形成了一定的势力时,就会对那些心里想着趁乱抢钱的人造成一定的威慑。就算是刚开始有一两个人捡了钱到自己口袋里,在这种情况下也不得不拿出来。所以反而结果是一张都不少了。

从众真是一件很奇异的事情。

什么叫头头?头头就是很够很好地利用这种从众心理,操纵众人干成一些事的人。不论是好事还是坏事。^O^

万恶的CET终结了?

今天的早间新闻报道说《英语四六级证6月取消》。老罗说“万恶的户口制度”,其实CET也差不多是万恶了。这样的消息真是令人欢欣鼓舞啊。

不过且慢,表高兴得太早,先看看新的制度是什么样的:

今后大学英语四、六级考试将由原来的100分制改为710分的记分体制,不设及格线,不颁发合格证书,只发放成绩单;突出听说能力的考查;

原来是把原先的CET证书换成了成绩单,貌似换汤不换药嘛。

再看这段:

吴启迪说,四、六级考试变为社会比较关注的事情,有的学校把四、六级考试与毕业证书和学位证书挂钩。

高校跟四、六级考试的成绩和学位毕业证书挂钩,这是教育部从来没有要求过的

没什么好说的了。

英语四、六级考试是不赚钱的。

但是围绕这个CET,很多人靠卖参考书、搞培训、开讲座甚至直接卖考题、当枪手……社会主义好,社会主义好,社会主义国家为“人民”制造赚钱的机会。^O^

引用一个网友留言:

网易网友[218.3.94.*] 认为:
2005-02-26 10:09:57
好个屁啊,
现在60分跟70分的差距就不一样了,
竞争更激烈啊!!
还一个一个在叫好,
不要给这种手段给迷惑了!!!

我也来评老方

前天老方的一篇《小资,其实就是“无知”的代名词!》招来了RLK的《老方貌似有些疯了。》和mike_shi的《再批老方……hohohoho》两篇评论。虽然我也回复了,不过正好今天没什么可写的,就也来发一篇掺和一下。

对于西方的文化,我们不能只看到它好的一面,所以我赞成老方和RLK的说法,片面的了解就是一种无知。我们需要知道它现在的好,也要知道它过去的不好,这同样对我们是一种借鉴。当然还有更重要的一点就是老方和RLK都说到的:不要丢掉自己文化中好的方面。否则就无异于邯郸学步了。

对于mike_shi说老方用过去来评判现在,我觉得这是他对老方的误读。老方并没有批评西方现在好,只是对于那些只看到现在的好,没看到过去的坏的那些人有些批评罢了。

西方的历史是一部改恶从善的历史。中国的历史则是一部改善从恶的历史。

这句话出自《一位将军刘亚洲精彩的讲演:中国是一部改善从恶的历史 (绝强好文)》,颇有几分道理的说。

总有一些事故会让人想到政治

这几天看天下的链接空了,有点不习惯。还好还有KESO的昨日新闻可以看。

结果今天发现天天网摘也不行了,连KESO的昨日新闻也看不鸟了–估计要到明天才可以看到他用FURL之类做的。

老冒那里才知道看天下事故的内幕

这才想起来要开两会乐,大家还是低调一点比较好。

BTW:还是继续8log,见本帮首页

BTW2:天天网摘好乐。

价值,在不同的人眼里……

今天早上终于听到夏丹MM在讨论新浪盛大的事了,还特别强调了一下新浪的毒丸计划。隐隐看出盛大此举始终非主流社会的价值观所能接受的。

几个月前我8了一篇《立场问题》,价值也是这样一个与立场有关的问题。KESO在“Playboy收购了《纽约时报》”的比喻被很多人垢病之后,又换了一个新的比喻:“温州鞋厂的老板,买下了《21世纪经济报道》”。结果在回复中,Datou立即表示了反对。

就拿温州鞋厂和《21世纪经济报道》来说,对于“上流社会”人士来说,他们根本不屑于穿温州鞋厂的低档鞋,但他们不能不看《21世纪经济报道》,温州鞋厂收购《21世纪经济报道》在他们看来,就像是一个农民工娶了一个白领MM。但对于偶们穷人来说,《21世纪经济报道》可以不看,但温州的便宜鞋却不能不穿。这就是二者的立场差异,导致了对价值评估的差异。

当然,站在传媒业者们的立场上,这是一次娱乐资本对严肃传媒的强奸。但是从资本的立场上看,盈利是唯一的目标。更何况新浪被这样的强奸也不是一回两回了,奸啊奸啊的早就已经习惯了。王志东跑路后不也有很多人说新浪将不再是原来的新浪了吗?虽然新浪后来搞了很多增加盈利的周边业务,但主业并没有受到很大的影响。阳光卫视与新浪战略结合也没有把新浪搞成一个电视台。

传媒业者们的担心还有一个重要的方面,那就是盛大入主新浪是否会导致新浪从严肃传媒堕落为庸俗娱乐。因为这一次与以往有点不同,盛大的口气太大,陈天桥一心想要控制新浪,所以也不由得不让人担心他会在得到控制权后改变新浪的“核心价值”。但两亿多美元对盛大来说也不能算是一个小数了,除非盛大收购新浪只是想毁掉它,否则它应该知道改变新浪这么多年建立起来的核心价值,无异于将这笔钱丢到水里。

早间新闻说汪延绝不会退出,貌似当年王志东出局前的消息也很类似。在新浪,一边是“毒丸”,另一边是老段的退出

事情越来越有趣乐。

很久没8乐

我和刺猬上个月的预言现在看来是一语成谶。

帮主已经是完全顾不上8LOG事业乐,连她自己的8LOG都开始长草乐。

yili说:~blg上都是贱宁,所以我不上了~~

色色作为本帮MM的公共彩旗,也是忙得很~~~

刺猬~~青岛的小嫚们还在等着他呢

打火机解除留帮察看这种事都拖了两天才公布~~~~~~~

BTW:最近的唯一亮点大概就是元宵茶话会上集体电话骚扰C彩旗乐。

程序员文化,gigix VS banq

我在《关于程序员文化,我看张恂VS熊节之争》中说到gigix很不厚道地把那篇批JdonFramework的BLOG发到《程序员》第二期上的事。虽然他在文中用了一个“X框架”来代替,但这个Jdon事件也不是一天两天了,很多人都是心知肚明的。

如果单纯从技术角度出发,gigix的批判是有理有据的。只是批判的方法实在有失厚道,结果当然也招来了不必要的麻烦,甚至有人在回复里爆了gigix的简历。但gigix的拥趸军也是很强大的,比如死胖子贴的这个方洪贱的《架构师的坏味道》就是一个强贴。

不过搞技术的,在碰到这种“硬技术”问题,较起真来通常都是这样的。想当年偶在CSDN也干过不少。

这位Banq.彭可比张恂惨。张恂谈的那些工程工艺之类的,都是“软技术”,本来没有什么定论。Banq.彭的可是有硬碰硬的代码在这里。这大概就是gigix对这两个人的做法完全不同的一个重要原因。

所谓程序员文化,大概也就只有代码这样的硬技术才能算是其中的核心啊。