AOP企业级应用思考 之 基于静态织入的容器式动态拦截

谈到AOP,大家乡到的一般就是两种主流的实现方式--动态代理和静态织入。两种主流的方式应该说各有优缺点,前者性能好,但是较难实现,稳定性不高;后者,更稳定,但是限制颇多,且执行效率不高。在本文中,Teddy将从动态织入和静态织入的技术区别出发,通过一个生动的故事,试图探索一种兼俱动态织入和静态织入的优点,并避免其缺点的全新的动态拦截思路。

1. 引言

说到“拦截”,就是Interception,实际上是一个AOP中的概念,大家一定不陌生,指在一个Method的已有代码块中插入额外的代码,比如可以插在Method代码的开始或者返回之前。

而说“动态拦截”,也就是说被插入的额外代码在程序运行时被插入到已有类的指定的Method的代码块的指定位置。一般而言,如果有一组框架工具能够支持我们非常方便的使用这种动态拦截,我们可以称这样的框架为一个支持动态织入AOP框架。最常见的这类AOP框架如Castle Project 中的AspectSharp。相对于这类在运行时进行动态织入的AOP框架,还有一类AOP框架被称为基于静态织入的AOP框架,这类框架一般在程序运行之前的编译时或者编译后在IL的级别修改二进制程序集的方式来插入额外代码。Teddy’s AspectWeaver就是一个典型的基于IL级别静态织入的AOP框架。这两类AOP框架应该说各有利弊。

动态织入方式一般基于动态代理的思想,即在运行时动态构造一个原有类的子类,这样就可以在子类的重载方法中插入额外代码。这样的好处是,一方面,避免了操作IL,程序会更可靠,框架的实现也更容易;另一方面,运行时可以动态的更换被织入的额外代码,可以有选择的在需要的时候使用被织入后的类或使用原来的类。但是,这种方案也有其固然的不足,特别是在.Net下,因为,.Net中,除非父类方法被定义为virtual,或者方法定义于某个接口,否则就不能被重载,这就是得“拦截”并不是可以对任意的方法进行的。同时,由于在运行时,AOP框架要动态的构造类和程序集,如果大量应用,难免有一定的性能损失。还有一些因为不能突破的C#或VB的语言描述能力的限制和OO设计模式的限制,这里就不一一列举了。

而静态织入方式则有非常大的灵活性,可以突破程序语言的描述能力,突破OO设计模式,可以拦截所有的方法甚至构造函数或属性访问器,因为它是直接修改IL。还有,因为它在运行前修改原有程序集,也就基本不存在运行时的性能损失问题了。它的不足,一方面是框架较复杂,实现较麻烦,依赖于对底层的IL指令集的操纵;另一方面,不能在运行时动态的替换被插入代码(当然也不能说完全不能,比如可以通过反射,在后台LoadWeaveReload程序集的方式达到一定的“动态”,但总没有前一个方案方便)。

2. 目标

那么下面就是本文的主题了,让我们回到这个拗口的标题,什么叫“基于静态织入的容器式动态拦截”呢?首先,让我们运用中文语法来分解这个短语,主语是:“动态拦截”;有两个修饰语:“基于静态织入”和“容器式”。既然还是“动态拦截”,那么,至少应该包含前述基于动态代理的动态织入方式的“动态”优点,那么这两个修饰语可以带来什么呢?我希望的是可以避免前述方案的不足,更甚至兼具静态织入方案的所有主要优点。您一定要说这个可能有些难度了~~那么姑且先别评价,让我们一起来看看我的方案能做到什么程度吧!

3. 正文

让我们从一段代码开始!

首先,我们有一个描述一只可爱的小狗的类Dog,我们的小狗有一个名字叫Name,它可以开心地叫唤Bark,它也可以快乐地奔跑Run

public class Dog
{
    
public string Name;
    
    
public void Bark()
    
{
        Console.Write(“I’m ” 
+ Name);
    }


    
public void Run()
    
{
        
//Do running
    }

}

现在我们要拿我们的小狗开刀,做点拦截了!但是我们的目标是和传统的拦截不同的:首先,我们希望拦截代码在运行时动态指定和绑定(静态织入似乎不易达到),其次,我们的方法没有定义成virtual,在不能确定天底下特殊的小狗会不会有不同的叫声或者有不同的奔跑姿势之前,上帝――也就是我,为什么要把它定义成virtual呢,更假如在我们的系统中,只需要区分这是小狗或者小猫,完全没必要知道他是哪种特殊的狗品种,那定义成virtual有多大必要呢(动态代理看来又无能为力了)?

上帝能解决这个难题吗?他冥思苦想,嘿嘿一笑:话说,上帝在创造世间一切事物之初,认为世间万物归于唯一,那就是我们无比纯洁的万物的本源——System.Object,上帝想,我今天为了让一只小狗能够多干点我要求的事而后起脸皮改造它,想当初不如在这些小小的生物身上按几个后门该多好啊(天那,感觉上帝是黑客转世)!可怜对小小的人类来说,“想当初”是徒劳的,但上帝是万能的,所以,上帝二话没说,取出“月光宝盒”——致西方读者或不知“月光宝盒”为何物的读者,基本上这东西就是西方科幻片中所谓的“时间机器”,就是对它作一定的Configuration,进行正确的操作就可以让某人可以回到从前的那种仪器设备:)。

大家可以想象,我们万能万恶的黑客上帝,回到了他创造世间万物之时,对着自己从娘胎里带来的System.Object呼了一口气,System.Object成了System.ObjectWithBackDoor。哈哈,上帝仰天长啸一声,回到了现在,再发出一层圣洁的光芒,微微调整了一下世间万物的做事方式。于是,小狗还是那只小狗,但是,是一只命运可怜的多的小狗了:

public class Dog : System.ObjectWithBackDoor
{
    
public string Name;
    
    
public void Bark()
    
{
        InlineAtStartHandler[“Bark()”]();
        Console.Write(“I’m ” 
+ Name);
        
InlineBeforeReturnHandler[“Bark()”]();
    }


    
public void Run()
    
{
        InlineAtStartHandler[“Run()”]();
        
//Do running
        InlineBeforeReturnHandler[“Run()”]();
    }

}


public class ObjectWithBackDoor
{
    public static DelegateHandlerCollection InlineAtStartHandler;
    public static DelegateHandlerCollection InlineBeforeReturnHandler;
    
    
//Other
}

可以看到,小狗可怜,因为它再没有全部的自由了,不但被安上了后门,还被改造了所有做事的方式,无论他想干什么,在干的前后,都不受自己控制地受到了木马监控。上帝满意的看了看自己的成果,做了点小小的试验:

Dog g = new Dog();
Dog.InlineAtStartHandler[“Bark()”] 
+= DoSomeCrying();
Dog.InlineBeforeReturnHandler[“Run()”] 
+= DoSomeBark();
g.Bark();
g.Run();

看着可怜的小狗每次叫唤之前都会先号啕大哭,而每次奔跑结束之前,则不可思议的先号啕大哭再叫唤,上帝得意地笑~~ 得意的笑~~

好了,各位观众,故事讲完了,我们回到正题。上帝,不,我是说我是怎么将小狗变成最后那副德性的呢?回想我们的第一个修饰词“基于静态织入”,不错,静态织入!只需要使用静态织入修改小狗的基类,并在每个方法的执行前后插入handler。而执行这些修改是不需要用户手动来调用静态织入器的,可以由一个应用服务“容器”来执行。当小狗所在的程序集被部署到应用服务容器中时,小狗在不知所以的情况下被改造了(由容器的管理工具来部署并在后台执行改造——静态织入)。于是,只要在这个容器中,就可以操作我们的后门——handler,来进行动态拦截了,我们可以随心所欲的增加或减少拦截。

来比较一下结果和之前的目标:很好!额外代码可以被方便的动态织入;没有运行时的子类动态生成,所以没有性能损失;对非virtual方法进行了拦截,而且看起来可以对任何类型、任何位置进行拦截。有什么是目标中有,而我们没有做到道的吗?:)

谢谢观赏!

-
欢迎关注我的AOP Framework: Teddy's AspectWeaver

posted @ 2005-10-19 16:09  Teddy's Knowledge Base  Views(6193)  Comments(31Edit  收藏  举报