关于动态代理

2016-02-19 18:50 3 1 收藏

有一种朋友不在生活里,却在生命力;有一种陪伴不在身边,却在心间。图老师即在大家的生活中又在身边。这么贴心的服务你感受到了吗?话不多说下面就和大家分享关于动态代理吧。

【 tulaoshi.com - 编程语言 】

本来想上周末没能用DELPHI实现动态代理就算了,可是这几天却始终放不下这个想法,这实在是一个太美妙的想法了。而且在认真看了VCL对SOAP的实现后,现在至少有九成的把握可以实现这样一个动态代理。

  那么动态代理有什么用?

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  这要先从GoF的Proxy模式说起。

  假设有下面这样一个接口及其实现:

  现在,如果你是这个接口的用户,而这个接口及其实现者提供了一个:

  Foo : IFoo;

  给你,其中Foo指向TFooImpl的一个实例。现在你有了IFoo的定义,和这个Foo实例--注意,你没有TFooImpl的定义和实现代码。如果现在要求你为所有的IFoo.doSth增加事务功能(假设doSth被实现为对数据库作更新操作),要怎么办?

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

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

TStaticProxy = class( TInterfacedObject, IIFoo )private  FImpl : IFoo;public  constructor Create( aImpl : IFoo );  function doSth( ... ) : xxx;  function bar( ... ) : xxx;end;constructor TStaticProxy.Create( aImpl : IFoo );Begin  FImpl := aImpl;End;function TStaticProxy.doSth( ... ) : xxx;Begin  BeginTransaction;  Result := FImpl.doSth( ... );  EndTransaction;End;function TStaticProxy.bar( ... ) : xxx;Begin  Result := FImpl.bar( ... );End;

  然后,在所有需要用到Foo对象的地方,改用新的NewFoo对象,如下:

var  NewFoo : IFoo;Begin  NewFoo := TStaticProxy.Create( Foo ) As IFoo;  ...  //  之后就可以把NewFoo完全当作Foo一样使用了。End;

  可见,我们通过了一个Proxy类代理了所有对IFoo接口的操作,相当于在从IFoo到TFooImpl之间插入了自己的代码,在某种程度上,这就是AOP所谓的“横切”。当然如果你能有TFooImpl的代码的话,就简单了,只要如下:

  从TFooImpl派生一个TNewFooImpl,然后在其中Override一下TFooImpl中的doSth即可,然后就创建TNewFooImpl的实例来代替Foo引用即可。

  但问题就在于你必须拥用TFooImpl的代码,并且可以变更所提供的Foo实例,但这在很多时候是做不到的--除非不是用DELPHI,而是如 Python一类的动态语言^O^。比如组件容器,比如远程实例等。还有像“虚代理”(就是当创建FImpl代价很高时,就在创建时只创建代理类,然后在真正需要时才创建FImpl的实例)

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

  特别是像组件容器或是通用远程代理这样,对要实现的接口并不能确定的情况下,静态代理一点用也没有。

  所以我们需要“动态代理”。我是在看了GIGIX发表在今年第一期《程序员》上的《动态代理的前世今生》一文后,虽然他说是的JAVA在 JDK1.3中提出的,在java.lang.reflect中的proxy。但这却让我突发奇想,发现其实完全可以在DELPHI里也实现这样一个动态代理。

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

  这样,我们只需要把要增加在功能做成一个IInvocationHandler接口的实例,如图中的TFooInvHandler。然后动态创建一个支持IFoo接口的TDynamicProxy的实例--它是一个动态代理,只需要传入相应的参数:要实现的接口和相应的InvHandler实例即可,不需要为每个接口写一个代理。当然如GIGIX文中所说,对于C++来说,这个可以用模板实现,但问题在于模板归根到底是一种编译时的动态化技术,对于组件容器这样需要运行时动态化的应用,它还是不能实现。最后,InvHandler通过RTTI去调用具体的实现Foo。

  它的用法大致如下:

TFooInvHandler = class( TInterfacedObject, IInvocationHandler )private  FImpl : IFoo;public  constructor Create( aImpl : IFoo );  function Invoke( IID, MethodName, Args[] ) : xxx;end;constructor TFooInvHandler.Create( aImpl : IFoo );Begin  FImpl := aImpl;End;function TFooInvHandler.Invoke( IID, MethodName, Args[] ) : xxxBegin  If ( IID = IFoo ) AND ( MethodName = 'doSth' ) Then  Begin BeginTransaction; Result := DoInvoke( FImpl, IID, MethodName, Args[] ); EndTransaction;  End  Else Result := DoInvoke( FImpl, IID, MethodName, Args[] );End;var  Handler : IInvocationHandler;  NewFoo : IFoo;Begin  Handler := TFooInvHandler.Create( Foo );  NewFoo := TDynamicProxy.Create( TypeInfo(IFoo), Handler ) As IFoo;  ...  //  之后就可以把NewFoo完全当作Foo一样使用了。End;

  注意:其中IInvocationHandler接口我还没想好要怎么定义,所以这段代码只是大致说明一下问题。另外,其中的DoInvoke就是通过RTTI来调用FImpl的。

  从上面的代码可以看到,TDynamicProxy通过参数IFoo动态生成了一个对IFoo接口的代理,并且通过Handler参数插入一个处理接口IInvocationHandler,由TDynamicProxy把对IFoo接口的调用全部转成对IInvocationHandler接口的调用,最后由TFooInvHandler类来视情况处理。在这里,可以通过运行时配置方式来动态决定是否需要切入事务所处理,需要对哪个接口的哪个方法切入。

  有了这样一个动态代理,还可以很方便地在InvocationHandler里切入如安全性检查,LOG等。这样的话,用DELPHI来实现AOP也不成问题了。

  现在我面临的问题就是:如何来定义这个IInvocationHandler。

  其实这里最主要的问题就是参数的传递的问题。接口可以用IID表示,方法可以用方法名,但参数变化太多了:一是数量不确定,可以有任意多个参数;二是类型不确定;三是传值参数和引用参数的问题。如前面那个例子用的是简单的办法,就是用一个不定长的Variant数组来记录,可以解决前两个问题,但第三个问题就比较麻烦,难道要用一个Tuple来作返回值?太麻烦了吧。

  在VCL的SOAP实现里是通过一个TInvContext在记录的,但这样的话对于Handler的开发者来说,就不得不面对TInvContext的内部复杂性,易用性太差。

  这就是我现在还不能确定实现的那一成。-_-|||

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

来源:https://www.tulaoshi.com/n/20160219/1619137.html

延伸阅读
一、使用的背景(也不能算是使用的背景,最多只能算是一个在什么条件下面我想到了使用动态代理实现AOP的拦截功能):因为在项目中程序的结构是使用SOAP调用JNI,因此在SOAP服务端里面没有任何实现代码,仅仅是new一个JNI的对象,然后调用JNI对应的方法。但是在调用JNI方法之前需要对传进JNI的JavaBean进行初始化,而且还需要记录日志。而SOAP服...
记忆是衍生自Lisp,Python,和Perl等过程性语言的一种设计模式,它可以对前次的计算结果进行记忆。 一个实现了记忆功能的函数, 带有显式的cache, 所以, 已经计算过的结果就能直接从cache中获得, 而不用每次都进行计算. 记忆能显著的提升大计算量代码的效率. 而且是一种可重用的方案. 本文阐述了在Java中使用这一模式的方法,并提供...
标签: Web开发
熟悉javascript的朋友对Eval()函数可能都不会陌生,我们可以用它来实现动态代码的执行,我自己甚至写过一个网页专门用来计算算术表达式的,计算能力上比google、baidu的计算器还要好一些,至少精度要高,但是如果超出了四则运算的话,表达式的形式会复杂很,比如以百度给出的例子: log((5+5)^2)-3+pi需要写成Math.log(Math.pow(5+5,2))*Math....
要实现对数据库的操作,离不开数据源(DataSource)或者连接(Connection),但是通常来说对数据库的操作都应该放在DAO中,而DAO又不应该与应用服务器相关联,所以一般都使用连接(Connection)。现在我们这里就有一个问题了,怎么在拦截器中获得连接。我想可以通过两种方式获得: 在分别讨论这两种方法之前,我们需要先讨论一下在处理数据库...
数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连接池都要求用户通过其规定的方法获取数据库...

经验教程

347

收藏

27
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部