向来我是“拿来主义”,即只管拿来用,不管正在用的东西是怎么实现的。最近由于一直想把 AOP 以及 Io C 等技术加入到项目中,因此对这些技术相当关注。后来选择了CastleProject中的DynamicProxy作为关注对象。不过这次起了贪心,不想再只知道使用,不知道如何实现了,于是开始深入去查看Castle是如何实现 AOP 的。
Proxy 是实现 AOP 的途径之一,通过代理可以有效的拦截某个类方法的执行过程。
从Castle中的测试例子可以看出,Castle使用了动态代码生成来对需要拦截的类生成一个代理,从而达到织入的目的。但是Castle是如何织入的呢?我打算使用一个简单的例子来测试Castle动态生成的代码。
对于传统的代理模式,可以得出如下的类图:
图1
上图的类的代码分别如下:
图2:类A的代码
图3:类RealANoVirtual的代码
图4:类ProxyClass的代码
然后,在使用这些时,可以如下编写代码:
图5:运行代码
运行上面的代码,我们就可以特出如下的结果:
图6:运行结果
通过代理,可以透明的使用DoSomething方法。
那么很显然,Castle也需要生成这样一个代理类,从而能够在调用DoSomething之前调用用户指定的代码。因此Castle提供了DynamicProxy以及Interceptor来达到目的。其中DynamicProxy会生成一个代理类,代理DoSomething操作,而在代理DoSomething操作时,使用用户指定的Interceptor达到拦截操作的目的。
为了看到Castle生成的动态代理是怎么样的,我写了一个类:RealA,代码如下:
图7:RealA代码
RealA中只有一个方法,DoSomething。我需要在DoSomething操作前后达到与前面的传统的模式一样的效果,按照Castle的要求,我需要写一个Interceptor,从而能够拦截前后的操作,代码如下:
图8:Interceptor代码
暂时不去考虑ProxyInterceptor是怎么一回事,继续往下。
有了需要拦截的类与Interceptor,那么直接就可以使用了,如下的代码:
图9:运行代码
图10:运行的结果
Bingle!!!,成功了。
那么Castle究竟做了什么呢?
在运行时,Castle会生成一个Assembly,放在应用的运行目录下,名称为:GeneratedAssembly.dll。通过反编译这个Assembly,就可以看到Castle究竟做了什么。
为了更好的说明,我把反编译后的代码分段说明:
1、Castle生成了一个代理类CProxyTypeRealA0,代理类继承自RealA:
public class C Proxy Type Real A0 : Real A
{
...
}
2、在CProxyTypeRealA0类中如下定义了一些Field:
图11:Field定义
其中的delegate的定义如下:
图12:delegate的定义
那么,这些Delegate的目的是什么呢?通过CProxyTypeRealA0的构造函数中的代码可以看出用途:
图13:构造函数
这里,我们关注的是DoSomething。从上面的构造函数代码可以看出,delegate指向了callback__DoSomething这个回调函数,这个函数的代码如下:
图14:回调函数代码
在这个回调函数中,就直接调用了RealA的DoSomething方法。
那么,Castle是如何调用到RealA的DoSomething的方法的呢?
3、调用链
在CProxyTypeRealA0中,覆盖了RealA的DoSomething方法,如下:
图15:覆盖的DoSomething
从代码中可以看出,DoSomething中调用了本地方法_Method2Invocation获取一个Invocation,然后调用Interceptor执行一些动作。
仔细看,在_Method2Invocation调用时传入了一个参数this.cached1,这个参数是一个delegate对象,并且在构造函数中指向了回调函数callback_DoSomething。这是一个很关键的地方,因此我们有必要看看_Method2Invocation作了什么。
图16 Method2Invocation的代码
Method2Invocation方法中,创建/获取了一个MethodInfo对应的Invocation,而Invocation封装了传入的delegate等相关信息。
很显然,封装这些信息是为了更好的传递。从Method2Invocation出来,接下去就是调用在构造函数中传入的Interceptor。
Interceptor的Intercept方法需要两个参数,一个就是前面封装好的Invocation对象,另一个是一个参数数组。目前看不出这个参数数组有什么用。
那么Intercept方法作了什么
看看Interceptor的实现代码,我的例子中的Interceptor直接继承自StandardInterceptor,所以先看看StandardInterceptor的代码:
图17 StandardInterceptor的代码
从代码中可以看出,这是一个模板模式。分别调用了PreProcess,PostProcess方法。这样就可以调用了我覆盖的两个方法。但是,在这里依然没有看到调用了DoSomething方法,别急,代码中调用了传入的Invocaion对象的Process方法。那么这个方法作了什么?
由于在Method2Invocation中,创建的是SameClassInvocation对象,因此,直接看SameClassInvocation的代码:
图18 SameClassInvocation的代码
Bingle!!! Process方法调用了传入的delegate对象,这样就调用到了回调函数callback_DoSomething,而回调函数又调用了RealA的DoSomething方法。这样,一个完整的拦截过程就实现了。同时,这里也看到前面的参数数组就是DoSomething的
参数数组,只不过例子中没有参数,所以为零了。
至此,我们看到了Castle完成代码织入的整个过程:
首先通过代码生成完成一个代理类,该代理类继承自要织入的类。然后在代理类中覆盖要拦截的方法,并在覆盖的方法中封装Invocation对象,并传给用户传入的Intercepter对象的Intercept方法。在Intercept方法依次调用Intercepter的PreProcess,通过Invocation传入的Delegate指向的回调函数,Intercepter的PostProcess方法,从而达到拦截的目的。
下面是例子的代码下载:
http://www.zeroport.net/files/DynamicProxy.zip
示例在Snippet Comiler 2.0 中编译运行通过。编译时需要自行添加对Castle.DynamicProxy.dll的引用。
如果在VS.NET中运行,需要自己创建Project,并加入压缩包中的文件。