First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 2984 评论 :: 339 引用

之所以学习DynamicProxy的原因有两个:一是在2005年第一期《程序员》杂志中的一篇文章《动态代理的前世今生》,另外就是对wayfarer的《让僵冷的翅膀飞起来》系列文章的讨论。这两方面都把我引向了对动态代理的学习。《动态代理的前世今生》一文是针对Java来写的,读了好几遍还是无法理解某些细节,只知道动态代理技术是很多新技术的基础,包括AOP。于是自己找来了网上的开源项目Castle's DynamicProxy for .net(Code Project上有文章介绍,但不很详细),花了些时间读读源代码,总算有所了解。不过要想深入学习,就要透彻的掌握IL以及通过Reflection动态生成程序集的技术,这不是我想要的,于是就此打住。通过对DynamicProxy的学习,对其中的关键技术总算了解了一二,写出来与大家分享。

DynamicProxy(无特殊说明的化,都指Castle's DynamicProxy for .net中的DynamicProxy技术)是建立在动态程序集生成技术的基础之上的,也就是说程序在运行时动态生成IL代码并被编译执行。这方面的内容我就不说了,感兴趣可以找相关资料,我只说说DynamicProxy如何利用动态编译技术实现所需操作的。

先说说DynamicProxy是如何实现代码拦截的(有些象AOP的横切,其实另一个开源项目Aspect#就是建立在DynamicProxy这个特性基础上的)。假设我们有编写好的一个类:

public class SimpleClass
{
  
public virtual int DoSomething1()
  
{
    SomeDelay(
50);
    Console.WriteLine(
"The return value of DoSomething1 is : {0}"100);
    
return 100;
  }

  
public virtual int DoSomething2()
  
{
    SomeDelay(
60);
    Console.WriteLine(
"The return value of DoSomething2 is : {0}"200);
    
return 200;
  }

  
private void SomeDelay(int t)
  
{
    Thread.Sleep(t);  
  }

}

该类有两个virtual方法DoSomething1与DoSomething2。现在要求不改变原有类的基础上为这个两个虚拟方法添加计时功能。其实我们可以非常容易的实现该功能,那就是继承SimpleClass,并为子类添加计时功能,代码如下:

public class DerivedSimpleClass : SimpleClass
{
  
public override int DoSomething1()
  
{
    DateTime begin 
= DateTime.Now;

    
//调用父类代码
    int result = base.DoSomething1();

    DateTime end 
= DateTime.Now;
    TimeSpan t 
= end - begin;
    Console.WriteLine(
"Time used: {0}\n", t.Milliseconds);
    
return result;
  }


  
public override int DoSomething2()
  
{
    DateTime begin 
= DateTime.Now;

    
//调用父类代码
    int result = base.DoSomething2();

    DateTime end 
= DateTime.Now;
    TimeSpan t 
= end - begin;
    Console.WriteLine(
"Time used: {0}\n", t.Milliseconds);
    
return result;
  }

}

我想,很多人已经开始对这段代码产生反感了。毕竟它里面存在了大量的重复代码,而这些重复代码又与原来的系统没有任何关系。那么我们能否将这些代码抽取出来,在必要的时候"插入"到SimpleClass中呢?让我们来看看动态代理是如何实现此功能的。

首先动态代理会动态生成一个Proxy对象,Proxy对象继承自SimpleClass(这也是为什么只有virtual方法才能被Intercept),生成出来的代码大体如下(经过大量简化和修改并只保留了DoSomething1方法。对此感兴趣的化,可以在Debug模式下反编译动态生成出来的GeneratedAssembly.dll文件):

using System;
using System.Reflection;

public class CProxyTypeSimpleClass : SimpleClass
{
  
// 通过委派机制实现回调
  public delegate int delegateForDoSomething1();
  
public delegateForDoSomething1 callableForDoSomething1 = 
    
new delegateForDoSomething1(callback__DoSomething1);

  
// 在此结合构造函数,并利用依赖注入技术实现自定义的拦截
  public IInterceptor _interceptor;
  
public CProxyTypeSimpleClass(IInterceptor interceptor)
  
{
    
this._interceptor = interceptor;
  }


  
//将对父类DoSomething1方法的调用封装到一个新方法中
  public virtual int callback__DoSomething1()
  
{
    
base.DoSomething1();
  }


  
// 复写DoSomething1方法,允许被拦截
  public override int DoSomething1()
  
{
    
/* 以下为示意性代码
     * MethodInfo m = ldtoke currentMethod;
     * object proxy = this;
     * IInvocation invocation = 
     *     ObtainInvocationFor(callableForDoSomething1, proxy, m);
     * return (int) _interceptor.Intercept( invocation )
    
*/
  
  }

}

在仔细研究这段代码之前,让我们先看一个拦截器的实现:

namespace Castle.DynamicProxy
{
  
using System;

  [Serializable]
  
public class StandardInterceptor : IInterceptor
  
{
    
public StandardInterceptor(){}
    
protected virtual void PreProceed(IInvocation invocation, params object[] args){}
    
protected virtual void PostProceed(IInvocation invocation, ref object returnValue, params object[] args){}

    
public virtual object Intercept(IInvocation invocation, params object[] args)
    
{
      PreProceed(invocation, args);
      
object retValue = invocation.Proceed( args );
      PostProceed(invocation, 
ref retValue, args);
      
return retValue;
    }

  }

}

这个拦截器使用了典型的模板方法模式,注意观察Intercept方法,其中的PreProceed与PostProceed方法是虚函数,可以在子类中被覆写。现在让我们看看DynamicProxy到底是如何工作的:

首先,动态代理CProxyTypeSimpleClass继承自SimpleClass并将原有代码进行"转移",下面的代码演示了父类DoSomething1方法被转移到了callback__DoSomething1方法中,腾出来的DoSomething1方法用来进行"拦截"操作。

public class CProxyTypeSimpleClass : SimpleClass
{
  
// 将对父类DoSomething1方法的调用封装到一个新方法中
  public virtual int callback__DoSomething1()
  
{
    
base.DoSomething1();
  }


  
// 复写DoSomething1方法,允许被拦截
  public override int DoSomething1()
  
{
     ……
  }

}

拦截器必须实现IInterceptor接口。我们上面看到的StandardInterceptor便是一个拦截器。IInterceptor接口非常简单,仅有一个方法,其定义如下:

public interface IInterceptor
{
   
object Intercept( IInvocation invocation, params object[] args );
}

那么拦截器与代理之间到底是如何工作的呢?我们用下面的图来说明调用步骤:

 

(1)DoSomething1调用Interceptor的Intercept方法,并传递相关参数。参数有两个,一个是IInvocation类型参数invocation,我们现在不用去关心IInvocation是什么,只要知道它里面包含了一个指向callback_DoSomething1的委派就行了(其实是一个ICallable类型的对象,现在我们不必过多的去关心它)。另外一个参数是object[] args,提供对callback_DoSomething1调用时所需的参数。

在CProxyTypeSimpleClass示意性代码中,我们可以看到的

  // 通过委派机制实现回调
  public delegate int delegateForDoSomething1();
  
public delegateForDoSomething1 callableForDoSomething1 = 
    
new delegateForDoSomething1(callback__DoSomething1);

便是为回调作准备的。在DoSomething1()方法中,将委派callableForDoSomething1封装到了一个IInvocation对象里面:

     * IInvocation invocation = 
     
*     ObtainInvocationFor(callableForDoSomething1, proxy, m);

(2)在真正的调用前,允许用户插入自己的代码。用户甚至可以改变调用参数args的值。

(3)利用回调机制,调用真正的DoSomething1代码。首先拦截器会从IInvocation对象中解出指向callback_DoSomething1的委派callableForDoSomething1;然后进行调用。于是CProxyTypeSimpleClass的callback_DoSomething1被调用,进而转去调用base.DoSomething1(),于是SimpleClass的DoSomething1便被调用执行。

(4)在实际代码调用完成后,调用PostProceed方法。用户可以在此方法中拦截返回的结果"ref object returnValue",甚至可以改变调用结果的值。下面是StandardInterceptor类中PostProceed方法的定义:

protected virtual void PostProceed(IInvocation invocation, ref object returnValue, params object[] args){}

由此可见,我们在没有修改SimpleClass的基础上,通过动态代理实现了方法调用的拦截,并插入了一部分自定义的代码。

下面我们来分析一下DynamicProxy中的"依赖注入机制"。其实动态代理的一个迷人的地方就在于我们可以动态更换拦截器,而不用修改任何原有代码。设想我们现在不统计方法运行时间了,而是对方法调用次数进行累加计数。我们可以非常方便的通过更换Interceptor实现这个功能而没有必要每次都重新写代码。在DynamicProxy动态生成的代码中我们可以看到类似代码如下:

  // 在此结合构造函数,并利用依赖注入技术实现自定义的拦截
  public IInterceptor _interceptor;
  
public CProxyTypeSimpleClass(IInterceptor interceptor)
  
{
    
this._interceptor = interceptor;
  }

可以看到DynamicProxy为我们生成了一个构造函数,允许在实例化代理对象时指定一个具体的拦截器。这样"依赖"便被"注入"到CProxyTypeSimpleClass类中。

这篇文章先写到这里,在后续的文章中,我将继续深入介绍DynamicProxy的功能细节,包括Mixins技术和IInvocation接口。


附:

1、"Castle's DynamicProxy for .net"的源代码可以从http://www.castleproject.org/下载到。

2、本篇文章的完整代码下载:http://files.cnblogs.com/zhenyulu/DynamicProxy.rar

 

posted on 2005-01-17 15:50 吕震宇 阅读(9182) 评论(19)  编辑 收藏 所属分类: 其它

评论

#1楼  2005-01-17 17:04 wayfarer      
恩,不错,持续关注!

前段时间,花了很多精力才看完JGTM的AOP尝鲜三部曲。JGTM采用了.Net Remoting中的一些思想和技术来实现。而其中,Remoting就主要采用Transparent Proxy和RealProxy实现对象的代理,然后通过MessageSink来传递对象,并从中实现拦截机制。

不过,这种方法最大的局限就是被拦截对象必须继承ContextObject类。如果采用reflection应该是不错的选择。可惜最近都很忙,又没有关注aop了。

希望能有时间多研究研究。
  回复  引用  查看    

#2楼  2005-01-17 17:17 CoolBug      
希望能看到震宇兄的多多文章,每篇都有实在之处。
  回复  引用  查看    

#3楼  2005-01-17 17:29 progame [未注册用户]
nhibernate和我的persistore都用的是这个Castle DynamicProxy
它对要拦截的类的要求可以为虚而不仅仅是接口实现这点比其它的类似项目要好得多
  回复  引用    

#4楼  2005-01-17 18:23 柳三公子      
怎么看上去有点像decorator模式啊
  回复  引用  查看    

#5楼  2005-01-17 19:16 泡茶 [未注册用户]
同上,有些像用模版实现的Decorator ^_^
  回复  引用    

#6楼 [楼主] 2005-01-17 21:12 吕震宇      
从上面的介绍看是有些象Decorator,但与Decorator有本质的区别。

首先Proxy是动态生成的。它可以针对任意对象生成代理。而Decorator却需要手工编写代码。另外DynamicProxy还可以实现Mixins,可以将一个对象与另外一个实现了某接口的对象合二为一,Decorator不可以。第三,DynamicProxy的应用范围与Decorator也不相同。DynamicProxy可以对某个对象中所有虚方法实现拦截。而Decorator往往装饰个别方法。除此之外,从实现技术上也是有区别的。所以还是不能将DynamicProxy与Decorator等同看待。

  回复  引用  查看    

#7楼  2005-01-18 12:29 hbifts      
你可以看看我们的AOP。NET的实现。
也是用的 Reflection。Emit :)

http://www.cnblogs.com/hbifts/archive/2005/01/12/6464.html
  回复  引用  查看    

#8楼 [楼主] 2005-01-18 13:32 吕震宇      
@hbifts

呵呵,这个AOP.NET历史比我进驻博客园的历史还长:)

我已经下载了,准备研究研究。其实我打算学完DynamicProxy后就学习Aspect#,这回连这个AOP.NET一起研究吧。谢谢!
  回复  引用  查看    

#9楼 [楼主] 2005-01-18 13:37 吕震宇      
@hbifts

初步看了看,有点晕,好长的代码呀!尤其是EMIT那部分。看来要好好分析分析了。不知hbifts能否推荐一些易懂的EMIT相关资料呢?先谢谢了:)
  回复  引用  查看    

#10楼 [楼主] 2005-01-18 16:17 吕震宇      
@ 柳三公子、 泡茶

其实Decorator与DynamicProxy还真有千丝万缕的联系。从hbifts提供的线索中我找到了两篇文章,非常不错。第一部分是关于Decorator的,第二部分是关于Proxy的。

《A Taste of AOP from Solving Problems with OOP and Design Patterns (Part I) 》
http://blog.joycode.com/jgtm2000/articles/12103.aspx

《A Taste of AOP from Solving Problems with OOP and Design Patterns (Part II) 》
http://blog.joycode.com/jgtm2000/articles/13446.aspx
  回复  引用  查看    

#11楼  2005-01-19 15:37 hbifts      
呵呵。我也是自己看得。没什么资料:(
过几天考完了AOP。NET会再重启动的:)
到时候再交流~
  回复  引用  查看    

#12楼  2005-01-20 17:37 idior [未注册用户]
关注
  回复  引用    

#13楼  2005-01-24 18:57 frogman      
以前听吕老师讲课就感觉受益非浅,今天感觉更胜一筹。
  回复  引用  查看    

#14楼  2005-07-18 21:19 we [未注册用户]
english version ?

  回复  引用    

#15楼  2005-09-07 10:01 wayfarer      
castle官方网站上有本文的链接。呵呵:)

http://www.castleproject.org/index.php/DynamicProxy
  回复  引用  查看    

#16楼 [楼主] 2005-09-07 22:03 吕震宇      
真没有想到,呵呵。

最近我正在写一篇关于Ioc与Aop的文章,使用spring.net展示Ioc的强大威力--在不修改任何现有程序代码的情况霞,将一个本地应用程序“升级”成分布式应用程序。代码编写工作已经基本完成,文章正在写作中...
  回复  引用  查看    

#17楼  2005-12-05 21:30 米小波      
我也用动态代理做了一个Demo,
http://mixiaobo.cnblogs.com/articles/291115.html
欢迎大家多多赐教
  回复  引用  查看    

#18楼  2005-12-15 10:54 白板      
我对remoting的代理机制研究还不深入,大致知道透明代理构造消息,真实代理提供消息拦截更改机制,最后才调用真实对象.
为什么remoting可以访问任何方法,而不是只限于virtual的方法?为什么castle不采用那种机制?
我们是否可以做一个拦截机制类似Remoting的AOP容器?
  回复  引用  查看    

#19楼  2007-06-25 13:22 ilan [未注册用户]
I don't understand chinese... too bad I can't understand the good stuff here.

Thanks
  回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-09-07 22:17 编辑过
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: