谈到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指令集的操纵;另一方面,不能在运行时动态的替换被插入代码(当然也不能说完全不能,比如可以通过反射,在后台Load、Weave、Reload程序集的方式达到一定的“动态”,但总没有前一个方案方便)。
2. 目标
那么下面就是本文的主题了,让我们回到这个拗口的标题,什么叫“基于静态织入的容器式动态拦截”呢?首先,让我们运用中文语法来分解这个短语,主语是:“动态拦截”;有两个修饰语:“基于静态织入”和“容器式”。既然还是“动态拦截”,那么,至少应该包含前述基于动态代理的动态织入方式的“动态”优点,那么这两个修饰语可以带来什么呢?我希望的是可以避免前述方案的不足,更甚至兼具静态织入方案的所有主要优点。您一定要说这个可能有些难度了~~那么姑且先别评价,让我们一起来看看我的方案能做到什么程度吧!
3. 正文
让我们从一段代码开始!
首先,我们有一个描述一只可爱的小狗的类Dog,我们的小狗有一个名字叫Name,它可以开心地叫唤Bark,它也可以快乐地奔跑Run。
上帝能解决这个难题吗?他冥思苦想,嘿嘿一笑:话说,上帝在创造世间一切事物之初,认为世间万物归于唯一,那就是我们无比纯洁的万物的本源——System.Object,上帝想,我今天为了让一只小狗能够多干点我要求的事而后起脸皮改造它,想当初不如在这些小小的生物身上按几个后门该多好啊(天那,感觉上帝是黑客转世)!可怜对小小的人类来说,“想当初”是徒劳的,但上帝是万能的,所以,上帝二话没说,取出“月光宝盒”——致西方读者或不知“月光宝盒”为何物的读者,基本上这东西就是西方科幻片中所谓的“时间机器”,就是对它作一定的Configuration,进行正确的操作就可以让某人可以回到从前的那种仪器设备:)。
大家可以想象,我们万能万恶的黑客上帝,回到了他创造世间万物之时,对着自己从娘胎里带来的System.Object呼了一口气,System.Object成了System.ObjectWithBackDoor。哈哈,上帝仰天长啸一声,回到了现在,再发出一层圣洁的光芒,微微调整了一下世间万物的做事方式。于是,小狗还是那只小狗,但是,是一只命运可怜的多的小狗了:
好了,各位观众,故事讲完了,我们回到正题。上帝,不,我是说我是怎么将小狗变成最后那副德性的呢?回想我们的第一个修饰词“基于静态织入”,不错,静态织入!只需要使用静态织入修改小狗的基类,并在每个方法的执行前后插入handler。而执行这些修改是不需要用户手动来调用静态织入器的,可以由一个应用服务“容器”来执行。当小狗所在的程序集被部署到应用服务容器中时,小狗在不知所以的情况下被改造了(由容器的管理工具来部署并在后台执行改造——静态织入)。于是,只要在这个容器中,就可以操作我们的后门——handler,来进行动态拦截了,我们可以随心所欲的增加或减少拦截。
来比较一下结果和之前的目标:很好!额外代码可以被方便的动态织入;没有运行时的子类动态生成,所以没有性能损失;对非virtual方法进行了拦截,而且看起来可以对任何类型、任何位置进行拦截。有什么是目标中有,而我们没有做到道的吗?:)
谢谢观赏!-欢迎关注我的AOP Framework: Teddy's AspectWeaver
posted on 2005-10-19 16:09 Teddy's Knowledge Base 阅读(3489) 评论(30) 编辑 收藏 网摘 所属分类: AOSD
静态做拦截的织入,然后运行时控制拦截的实现,很好的想法!:) 期待你的AspectWeaver的稳定版本。^_^ 回复 引用 查看
哈哈 我之前也想到过这点,静态和动态确实可以结合的。 基于动态的只能在生成它的继承类上玩花样。 有了静态织入就不用再受继承的限制了。 good job! 我觉得在方面的定义上还是要做一些改进。 回复 引用 查看
@Cavingdeep: 现在的0.6版算是比较稳定了,但还没做过更大规模的测试,起码我自己做的测试到目前还没发现大的问题。 @idior: 你觉得Aspect的定义方面怎么改进好呢?我觉得已经挺简单了,如果对每种常用的情形都给各example的话,对用户来讲使用就不是很难了。 回复 引用 查看
有关动态静态结合我再说几句. 1. 简单的结合, 仅仅加上virtual. 然后再利用类似castle.aop的动态方式 实现更完善的aop 2. 根据dynamic proxy的实现机制(在子类中加入delegate). 用静态的方式织入这些代码,自己做一个dynamic proxy,然后在此基础上实现一个aop. 有关aspect定义的方式, 我现在还不清楚Teddy's AspectWeaver的格式究竟成什么样了. 相对而言比较喜欢aspect#的方式. 但是在定义上要充分的体现出Aop的各种概念,并且尽量的实现安全的有检查的定义 一直没时间看Teddy's AspectWeaver的源码,惭愧! 我会加紧的. 回复 引用 查看
@idior: 你说的两点固然是没错的,的确是可以借助静态织入扩展动态代理方式的织入,但是,如果你细看本文就能发现,实际上我写文章的时候思考的不是怎样AOP,而是,站在一个更高层面,即AOP在企业级应用方面的地位,或者说怎么为企业级的应用开发服务。注意文中多处提到的“容器”一词。没错,我是想构建一个企业级应用容器。AOP只是为这个容器服务的技术之一。稍后我会有后续文章来进一步叙述我的思想~~ 回复 引用 查看
精彩! 回复 引用
如果是谈容器,那么aop仅仅是实现手段之一,而且容器中的方法拦截也常被称之为aop. ejb,mts都有aop的概念。容器重点在于你能提供什么样的服务,而aop可以使得这一过程对用户更加的透明,可定制。 所以如果是做容器,那现在似乎还离得很远,还是应该做一个aop的基础设施比较现实。 回复 引用 查看
我提出的是一个思路,和传统的应用容器使用AOP的概念还是有本质不同的,可以称作“部署时静态织入拦截代理”,这对用户是完全透明的。可以想象一下这样一种应用情景,我完全独立开发了一个web 应用,比如博客园这样一个站点吧,我可以独立运行,也可以部署到我的容器来运行,不需要做任何额外修改,只需要通过部署工具完成部署,就能通过这个容器对其中的应用进行各个层面的监控,如果有多个应用甚至可以进行应用间的整合,思想就是我文中的例子,这个容器本身甚至也可以被部署到其他的应用容器中。 还有一点你注意到没有,我这个方案下,甚至不需要用户写任何的configuration!因为部署工具可以帮你做,你点两下鼠标就行了! 那么你觉得这是不是真的那么遥远呢? 回复 引用 查看
good job! 回复 引用
从本文的内容来看,并没有跳出.Net对AOP限制的框框。例子和我之前的AOP实现并无区别。 看teddy的描述,似乎准备用静态织入的方式,对System.Object动手术?感觉Teddy并没有把文章写完,也未曾解我的疑惑。不知Teddy是否还有续集? 回复 引用 查看
wayfarer兄可以去看看Teddy’s AspectWeaver的项目主页,估计很快就了解了 回复 引用 查看
@wayfarer: 你似乎也还没完全理解我的意图,我在思考写一个支持“部署时静态织入拦截代理”的应用服务器,而不仅仅是AOP!稍后我还会有文章来描述。 回复 引用 查看
我有一个非常有前途的方案可以实现这个想法!——CLR Runtime host .NET 2.0的Runtime host API极大地增强了,我想研究下一定可以找到实现这个功能机制。我想最佳的方案无过于“不织入”,因为织入是违反CLR抽象原则的,Assembly应当保持设计的原貌和结构,不可以从这个角度入手。而从Runtime来入手却十分可行,因为这个已经不在抽象层面,所以做什么手脚都不会影响到CLR的抽象原则。 回复 引用 查看
不过Runtime host API的代码安全性较高,不可以做太多“移花接木”的操作,不然会引起非法错误保护。不然在这个世界里动手脚能做的事远远不止这点。 回复 引用 查看
@装配脑袋: 你说的方案值得研究,不过,可以猜想,runtime host的机制肯定要被限制在clr的安全性之内,总不可能允许我修改System.Object的运行是代码吧?因此会有颇多限制。所以说,修改assembly至少是不会被禁止的,也是依然有其优势的。并且,我说的“部署时织入”,能够使用户在开发过程中不必担心程序及的il记得以外修改和不能调试,部署时插入的代码也是固定的代码(拦截代理代码),因此就能将静态织入的不稳定性降至最低。而且用户再同其中进行拦截监控是不需要传统AOP方案中那样的configuration的,直接通过handler就行,我在写一个原型,已有眉目了。 回复 引用 查看
难道说修改mscorlib.dll是可行的?不至于吧,这些组件都是签名的,岂容你修修改改?我想很多企业用户的组件也都会是签名的,放在GAC里的,可怎么办捏? 我想用Runtime host拦截托管方法调用然后插入自定义代码还不至于非法太多…… 回复 引用 查看
不是修改mscorlib.dll 而是修改对象的dll,让它不再仅仅继承object. 回复 引用
如果是这样,这很值得商榷阿,如果有依赖于类继承关系和反射的应用,会被破坏的。 回复 引用 查看
@装配脑袋: idior说得没错,我从来没想过要去修改系统assembly,我想就算runtime host再强也不会允许你修改mscorlib.dll,不然,.net就毫无安全性可言了!你说的拦截系统调用很吸引人,值得好好研究,但是我很怀疑能够做到什么程度~~ 我只是想修改用户程序集,你说得没错,无限制的应用静态织入可能会造成不可预知的问题,而本文中的方案就是将静态织入的风险降至最低的方案,但是却能获得更多的动态、和静态织入的好处! 回复 引用 查看
我对AspectWeaver直接在用户Assembly中织入的方式很是赞赏,因为使用这种办法可以使原有的商业逻辑丝毫不受影响,而在运行时却具有了“横切”Aspect功能。但或许是因为对Assembly、IL没什么把握的缘故,总有点毛骨悚然的感觉,呵呵 回复 引用 查看
感觉楼主的想法很有问题哦! 我们来几个假设,如果我们有100个组件的上千个方法都需要此功能,工作量巨大无比(超多的继承类),而且代码职责不清。 如果我们要对拦截的组件需要植入不同逻辑功能代码,或者不同逻辑功能的组合,比如调用Bark()方法时,需要将调用参数及方法名称写入日志文件,而调用Run()方法时,需要计算调用此方法的CPU处理时间开销。如果都需要在被拦截的方法里面去静态植入代码,可以想象。 回复 引用
@cyj: 过多的静态植入肯定是有风险的,本文论述的就是怎样最大程度的减少静态植入的危害,也就是只植入拦截代理,在额外的组件中定义aspect,这样就能最大程度的保证程序的可靠性,但是又可以发挥静态植入本身的优势。而且本文只是提出一个思路,具体实现的话,肯定是要考虑很多的简化措施。我也可以想象,如果你要以AOP的思路来解决你提到的这样的应用情景,用其他任何方式都不会比用静态植入更省事吧。 回复 引用 查看
我对AOP还是初步了解,但AOP是否真是有我们描述的这么强大吗? 由于我没有深入了解,所以才会产生疑问。 一个简单问题: 现在有一个插入数据的方法(由于是一步操作没有事务处理),现在通过AOP插入日志操作。现在就产生了一个新的问题,如何确何两个操作的事务性? 我所知道在。NET下只有ServicedComponent才能处理分布式事务,当然你是可以静态织入原有类派生于ServicedComponent(前提下是那个类必须没有继承其他类) 回复 引用
AOP式的代码插入实际上是一种特殊方式的设计模式,从本质上讲,如果你发现通过代码插入方式使得你业务本身的事务难以维护,或者代码更不易理解了,那么说明你用该模式的时机未必正确。 拿日志来讲,如果你要用代码插入来实现该功能,那么要么你要自己设计一种维护事务的方式,要么就用更好的模式组织代码。 回复 引用 查看
good ! 这里有个动态代理实现权限验证的例子 http://mixiaobo.cnblogs.com/articles/291115.html ,可以去看看 回复 引用 查看
不优雅 (1) 业务对象需要继承容器类, 依赖于拦截的实现 (2) 没有采用代理机制,无法更改消息 回复 引用 查看
@白板: 你说的这两个问题都不对。 1、这里,业务对象完全不需显示的继承容器类,这个积累改变为容器类的过程是在部署时容器自动在内部完成的。 2、这里没有采用代理机制正式优势所在,完全可以改变消息,比如函数F有一个参数A的值为a,我完全可以和代理方式同样的改变参数,这个拦截代理函数intercetFunc同样是在部署时容器自动插入的,用户不需为容器作任何代码修改,并且效率更高,因为没有构造代理类的性能损失: F(A) { //A==a interceptFunc(A); //change A to b in this func //A=b ... } 回复 引用 查看
哥,能否告知Ioc和AOP有啥区别吗? 回复 引用 查看
Ioc和AOP不能说完全不相关,但是,本质上是两种思想。尽管AOP可以极大地扩展IoC的能力。想要比较简单的理解和比较的话,可以看看castleproject中的Microkernal和aspect#,前者是一个典型的IoC,后者是一个典型的AOP。 回复 引用 查看
要做一个类似COM+的??? 回复 引用
Powered by: 博客园 Copyright © Teddy's Knowledge Base