热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

用Delphi实现动态代理(1):概述[MentalStudio]猛禽[Blog]一、问题所谓动态代理(DynamicProxy),要先从GoF的Proxy模式说起。假设有一个IFoo接口:{$

用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


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 一、MyEclipse中的一些常用的快捷键:ctrl+shift+x大写ctrl+shift+y小写alt+内容提示写住方法的时候可以先写main然后按alt+就可以了ctrl+1 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 《GOF设计模式》—命令(COMMAND)—Delphi源码示例:支持取消和重做(多次取消1)
    示例:多次取消1说明:      若要支持多级的取消和重做,就需要有一个已被执行命令的历史列表(historylist),该列表的最大长度决定了取消和重做的级数。历史列表存储 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java中处理大数据问题(BigInteger、BigDecimal)
    原文转自:https:blog.csdn.netzhongkeleearticledetails52289163;http:www.cnblogs.c ... [详细]
  • Reversedigitsofaninteger.(反转一个整型数)Example1:x123,return321Example2:x-123,return-3211 ... [详细]
  • 在实际项目中有时候需要判断输入的值是否全为数字,然而直接用判断数字的一些函数如Val()和Isnumeric()等对"+"号,"-"号,还有小数点不能直接过滤 ... [详细]
author-avatar
着袖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有