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