AOP 实现

向来我是“拿来主义”,即只管拿来用,不管正在用的东西是怎么实现的。最近由于一直想把 AOP 以及 Io C 等技术加入到项目中,因此对这些技术相当关注。后来选择了CastleProject中的DynamicProxy作为关注对象。不过这次起了贪心,不想再只知道使用,不知道如何实现了,于是开始深入去查看Castle是如何实现 AOP 的。

Proxy 是实现 AOP 的途径之一,通过代理可以有效的拦截某个类方法的执行过程。

从Castle中的测试例子可以看出,Castle使用了动态代码生成来对需要拦截的类生成一个代理,从而达到织入的目的。但是Castle是如何织入的呢?我打算使用一个简单的例子来测试Castle动态生成的代码。

Scene 我设想了一个例子,一个类有一个方法:DoSomething,这个方法调用时输出Yes, * is running DoSomething,其中 * 表示类的名称。然后我需要在这个类调用DoSomething之前及之后,还需要输出Now i'm starting Do Something以及 Now i'm ending DoSomething。当然,有很多方法可以实现,由于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,并加入压缩包中的文件。

你可能感兴趣的:(AOP)