DynamicProxy(动态代理)技术剖析(1)

之所以学习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 );
}

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

 DynamicProxy(动态代理)技术剖析(1)

(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://www2.cnblogs.com/Files/zhenyulu/DynamicProxy.rar

using  System;
using  System.Threading;
using  Castle.DynamicProxy;

[Serializable]
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);    
    }

}


public   class  MainClient
{
    
public static void Main()
    
{
        ProxyGenerator _generator 
= new ProxyGenerator();
        
object proxy = _generator.CreateClassProxy(typeof(SimpleClass), new TimeUsedInterceptor());
            
        
if(proxy is SimpleClass)
            Console.WriteLine(
"proxy is the subclass of SimpleClass.\n");

        ((SimpleClass) proxy).DoSomething1();
        ((SimpleClass) proxy).DoSomething2();

        Console.WriteLine(
"\n************************************\n");

        CounterInterceptor ci 
= new CounterInterceptor();
        proxy 
= _generator.CreateClassProxy(typeof(SimpleClass), ci);
        ((SimpleClass) proxy).DoSomething1();
        ((SimpleClass) proxy).DoSomething2();
        ci.ShowCallCounter();

        Console.ReadLine();
    }

}


public   class  TimeUsedInterceptor : StandardInterceptor
{
    
private DateTime begin, end;
    
private TimeSpan t;

    
protected override void PreProceed(IInvocation invocation, params object[] args)
    
{
        begin 
= DateTime.Now;
        
base.PreProceed(invocation, args);
    }


    
protected override void PostProceed(IInvocation invocation, ref object returnValue, params object[] args)
    
{
        end 
= DateTime.Now;
        t 
= end - begin;
        Console.WriteLine(
"Time used: {0}\n", t.Milliseconds);
        
base.PostProceed (invocation, ref returnValue, args);
    }

}


public   class  CounterInterceptor : StandardInterceptor
{
    
private int callCounter = 0;

    
protected override void PreProceed(IInvocation invocation, params object[] args)
    
{
        callCounter 
+= 1;
        
base.PreProceed(invocation, args);
    }


    
public void ShowCallCounter()
    
{
        Console.WriteLine(
"\nCalled: {0} times\n", callCounter);
    }

}

你可能感兴趣的:(dynamic)