Teddy's Knowledge Base

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 on 2005-10-19 16:09 Teddy's Knowledge Base 阅读(3489) 评论(30)  编辑 收藏 网摘 所属分类: AOSD

评论

#1楼  2005-10-19 17:25 Cavingdeep      

静态做拦截的织入,然后运行时控制拦截的实现,很好的想法!:)

期待你的AspectWeaver的稳定版本。^_^
  回复  引用  查看    

#2楼  2005-10-19 17:32 idior      

哈哈 我之前也想到过这点,静态和动态确实可以结合的。

基于动态的只能在生成它的继承类上玩花样。
有了静态织入就不用再受继承的限制了。

good job!

我觉得在方面的定义上还是要做一些改进。
  回复  引用  查看    

#3楼 [楼主] 2005-10-19 17:48 Teddy's Knowledge Base      

@Cavingdeep:

现在的0.6版算是比较稳定了,但还没做过更大规模的测试,起码我自己做的测试到目前还没发现大的问题。

@idior:

你觉得Aspect的定义方面怎么改进好呢?我觉得已经挺简单了,如果对每种常用的情形都给各example的话,对用户来讲使用就不是很难了。
  回复  引用  查看    

#4楼  2005-10-19 19:36 idior      

有关动态静态结合我再说几句.

1. 简单的结合, 仅仅加上virtual. 然后再利用类似castle.aop的动态方式 实现更完善的aop
2. 根据dynamic proxy的实现机制(在子类中加入delegate). 用静态的方式织入这些代码,自己做一个dynamic proxy,然后在此基础上实现一个aop.


有关aspect定义的方式, 我现在还不清楚Teddy's AspectWeaver的格式究竟成什么样了. 相对而言比较喜欢aspect#的方式. 但是在定义上要充分的体现出Aop的各种概念,并且尽量的实现安全的有检查的定义 一直没时间看Teddy's AspectWeaver的源码,惭愧! 我会加紧的.
  回复  引用  查看    

#5楼 [楼主] 2005-10-19 22:32 Teddy's Knowledge Base      

@idior:

你说的两点固然是没错的,的确是可以借助静态织入扩展动态代理方式的织入,但是,如果你细看本文就能发现,实际上我写文章的时候思考的不是怎样AOP,而是,站在一个更高层面,即AOP在企业级应用方面的地位,或者说怎么为企业级的应用开发服务。注意文中多处提到的“容器”一词。没错,我是想构建一个企业级应用容器。AOP只是为这个容器服务的技术之一。稍后我会有后续文章来进一步叙述我的思想~~
  回复  引用  查看    

#6楼  2005-10-20 08:22 moonriver [未注册用户]

精彩!   回复  引用    

#7楼  2005-10-20 09:31 idior      

如果是谈容器,那么aop仅仅是实现手段之一,而且容器中的方法拦截也常被称之为aop. ejb,mts都有aop的概念。容器重点在于你能提供什么样的服务,而aop可以使得这一过程对用户更加的透明,可定制。
所以如果是做容器,那现在似乎还离得很远,还是应该做一个aop的基础设施比较现实。
  回复  引用  查看    

#8楼 [楼主] 2005-10-20 10:37 Teddy's Knowledge Base      

我提出的是一个思路,和传统的应用容器使用AOP的概念还是有本质不同的,可以称作“部署时静态织入拦截代理”,这对用户是完全透明的。可以想象一下这样一种应用情景,我完全独立开发了一个web 应用,比如博客园这样一个站点吧,我可以独立运行,也可以部署到我的容器来运行,不需要做任何额外修改,只需要通过部署工具完成部署,就能通过这个容器对其中的应用进行各个层面的监控,如果有多个应用甚至可以进行应用间的整合,思想就是我文中的例子,这个容器本身甚至也可以被部署到其他的应用容器中。

还有一点你注意到没有,我这个方案下,甚至不需要用户写任何的configuration!因为部署工具可以帮你做,你点两下鼠标就行了!

那么你觉得这是不是真的那么遥远呢?
  回复  引用  查看    

#9楼  2005-10-20 12:07 四海为家 [未注册用户]

good job!   回复  引用    

#10楼  2005-10-20 17:41 wayfarer      

从本文的内容来看,并没有跳出.Net对AOP限制的框框。例子和我之前的AOP实现并无区别。

看teddy的描述,似乎准备用静态织入的方式,对System.Object动手术?感觉Teddy并没有把文章写完,也未曾解我的疑惑。不知Teddy是否还有续集?
  回复  引用  查看    

#11楼  2005-10-20 18:08 疾风      

wayfarer兄可以去看看Teddy’s AspectWeaver的项目主页,估计很快就了解了   回复  引用  查看    

#12楼 [楼主] 2005-10-20 20:41 Teddy's Knowledge Base      

@wayfarer:

你似乎也还没完全理解我的意图,我在思考写一个支持“部署时静态织入拦截代理”的应用服务器,而不仅仅是AOP!稍后我还会有文章来描述。
  回复  引用  查看    

#13楼  2005-10-21 08:33 装配脑袋      

我有一个非常有前途的方案可以实现这个想法!——CLR Runtime host
.NET 2.0的Runtime host API极大地增强了,我想研究下一定可以找到实现这个功能机制。我想最佳的方案无过于“不织入”,因为织入是违反CLR抽象原则的,Assembly应当保持设计的原貌和结构,不可以从这个角度入手。而从Runtime来入手却十分可行,因为这个已经不在抽象层面,所以做什么手脚都不会影响到CLR的抽象原则。
  回复  引用  查看    

#14楼  2005-10-21 08:35 装配脑袋      

不过Runtime host API的代码安全性较高,不可以做太多“移花接木”的操作,不然会引起非法错误保护。不然在这个世界里动手脚能做的事远远不止这点。   回复  引用  查看    

#15楼 [楼主] 2005-10-21 08:44 Teddy's Knowledge Base      

@装配脑袋:

你说的方案值得研究,不过,可以猜想,runtime host的机制肯定要被限制在clr的安全性之内,总不可能允许我修改System.Object的运行是代码吧?因此会有颇多限制。所以说,修改assembly至少是不会被禁止的,也是依然有其优势的。并且,我说的“部署时织入”,能够使用户在开发过程中不必担心程序及的il记得以外修改和不能调试,部署时插入的代码也是固定的代码(拦截代理代码),因此就能将静态织入的不稳定性降至最低。而且用户再同其中进行拦截监控是不需要传统AOP方案中那样的configuration的,直接通过handler就行,我在写一个原型,已有眉目了。
  回复  引用  查看    

#16楼  2005-10-21 14:27 装配脑袋      

难道说修改mscorlib.dll是可行的?不至于吧,这些组件都是签名的,岂容你修修改改?我想很多企业用户的组件也都会是签名的,放在GAC里的,可怎么办捏?
我想用Runtime host拦截托管方法调用然后插入自定义代码还不至于非法太多……
  回复  引用  查看    

#17楼  2005-10-21 14:32 idior, [未注册用户]

不是修改mscorlib.dll
而是修改对象的dll,让它不再仅仅继承object.
  回复  引用    

#18楼  2005-10-21 14:44 装配脑袋      

如果是这样,这很值得商榷阿,如果有依赖于类继承关系和反射的应用,会被破坏的。   回复  引用  查看    

#19楼 [楼主] 2005-10-21 16:05 Teddy's Knowledge Base      

@装配脑袋:

idior说得没错,我从来没想过要去修改系统assembly,我想就算runtime host再强也不会允许你修改mscorlib.dll,不然,.net就毫无安全性可言了!你说的拦截系统调用很吸引人,值得好好研究,但是我很怀疑能够做到什么程度~~

我只是想修改用户程序集,你说得没错,无限制的应用静态织入可能会造成不可预知的问题,而本文中的方案就是将静态织入的风险降至最低的方案,但是却能获得更多的动态、和静态织入的好处!
  回复  引用  查看    

#20楼  2005-10-25 14:46 疾风      

我对AspectWeaver直接在用户Assembly中织入的方式很是赞赏,因为使用这种办法可以使原有的商业逻辑丝毫不受影响,而在运行时却具有了“横切”Aspect功能。但或许是因为对Assembly、IL没什么把握的缘故,总有点毛骨悚然的感觉,呵呵   回复  引用  查看    

#21楼  2005-11-04 12:46 cyj [未注册用户]

感觉楼主的想法很有问题哦!
我们来几个假设,如果我们有100个组件的上千个方法都需要此功能,工作量巨大无比(超多的继承类),而且代码职责不清。
如果我们要对拦截的组件需要植入不同逻辑功能代码,或者不同逻辑功能的组合,比如调用Bark()方法时,需要将调用参数及方法名称写入日志文件,而调用Run()方法时,需要计算调用此方法的CPU处理时间开销。如果都需要在被拦截的方法里面去静态植入代码,可以想象。
  回复  引用    

#22楼 [楼主] 2005-11-04 13:13 Teddy's Knowledge Base      

@cyj:

过多的静态植入肯定是有风险的,本文论述的就是怎样最大程度的减少静态植入的危害,也就是只植入拦截代理,在额外的组件中定义aspect,这样就能最大程度的保证程序的可靠性,但是又可以发挥静态植入本身的优势。而且本文只是提出一个思路,具体实现的话,肯定是要考虑很多的简化措施。我也可以想象,如果你要以AOP的思路来解决你提到的这样的应用情景,用其他任何方式都不会比用静态植入更省事吧。
  回复  引用  查看    

#23楼  2005-11-14 16:22 henryfan [未注册用户]

我对AOP还是初步了解,但AOP是否真是有我们描述的这么强大吗?
由于我没有深入了解,所以才会产生疑问。
一个简单问题:
现在有一个插入数据的方法(由于是一步操作没有事务处理),现在通过AOP插入日志操作。现在就产生了一个新的问题,如何确何两个操作的事务性?
我所知道在。NET下只有ServicedComponent才能处理分布式事务,当然你是可以静态织入原有类派生于ServicedComponent(前提下是那个类必须没有继承其他类)
  回复  引用    

#24楼 [楼主] 2005-11-15 15:04 Teddy's Knowledge Base      

AOP式的代码插入实际上是一种特殊方式的设计模式,从本质上讲,如果你发现通过代码插入方式使得你业务本身的事务难以维护,或者代码更不易理解了,那么说明你用该模式的时机未必正确。

拿日志来讲,如果你要用代码插入来实现该功能,那么要么你要自己设计一种维护事务的方式,要么就用更好的模式组织代码。
  回复  引用  查看    

#25楼  2005-12-05 21:33 米小波      

good !

这里有个动态代理实现权限验证的例子

http://mixiaobo.cnblogs.com/articles/291115.html

,可以去看看
  回复  引用  查看    

#26楼  2005-12-14 21:47 白板      

不优雅
(1) 业务对象需要继承容器类, 依赖于拦截的实现
(2) 没有采用代理机制,无法更改消息
  回复  引用  查看    

#27楼 [楼主] 2005-12-15 06:40 Teddy's Knowledge Base      

@白板:

你说的这两个问题都不对。

1、这里,业务对象完全不需显示的继承容器类,这个积累改变为容器类的过程是在部署时容器自动在内部完成的。

2、这里没有采用代理机制正式优势所在,完全可以改变消息,比如函数F有一个参数A的值为a,我完全可以和代理方式同样的改变参数,这个拦截代理函数intercetFunc同样是在部署时容器自动插入的,用户不需为容器作任何代码修改,并且效率更高,因为没有构造代理类的性能损失:
F(A)
{
//A==a
interceptFunc(A); //change A to b in this func
//A=b
...
}
  回复  引用  查看    

#28楼  2005-12-17 10:10 蛙蛙池塘      

哥,能否告知Ioc和AOP有啥区别吗?   回复  引用  查看    

#29楼 [楼主] 2005-12-17 23:35 Teddy's Knowledge Base      

Ioc和AOP不能说完全不相关,但是,本质上是两种思想。尽管AOP可以极大地扩展IoC的能力。想要比较简单的理解和比较的话,可以看看castleproject中的Microkernal和aspect#,前者是一个典型的IoC,后者是一个典型的AOP。   回复  引用  查看    

#30楼  2006-08-19 22:32 jianyi0115 [未注册用户]

要做一个类似COM+的???   回复  引用    


发表评论



姓名 [登录] [注册] 
主页
Email (仅博主可见) 
验证码 *  验证码看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论   新用户注册   返回页首      

导航: 网站首页 社区 新闻 博问 闪存 网摘 招聘 .NET频道 知识库 找找看 Google站内搜索



China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
China-Pub 计算机绝版图书按需印刷服务

相关文章:

相关链接: