`
fujohnwang
  • 浏览: 153063 次
社区版块
存档分类
最新评论

Unveil Spring 连载 之 一起来看AOP

阅读更多

在我们真正进入Spring的AOP框架的“内心世界 ”之前, 我觉得有必要先对整个AOP的概念以及它的来龙去脉来道个明白,毕竟,一般情况您都了如指掌了,特殊的AOP实现产品又有何难那!?

3.1. 一起来看AOP!

软件开发自始至终就一直在寻求更加高效,更加易于维护甚至更易于扩展的方式来进行, 为了提高开发效率,我们对开发使用的语言进行抽象,走过了从汇编时代到现在各种高级语言繁盛之时期; 为了便于维护和扩展,我们就对某些相同的功能进行归类并使之模块化,冲出了最初的“原始部落 ”, 走过了从使用“过程化编程(Procedural Programming ) ”的开发方法到现在盛行的面向对象编程(OOP,Object-Oriented Programming)开发方法的“短暂而漫长 ”的历程。 但不管走过的路有多长,多么坎坷,我们一直没有放弃寻找更加完美,高效的软件开发方法,过去如此,现在亦如此。

当OOP/OOSD(Object-Oriented Software Development)被提出来以“取代[19 ] 过去的基于“结构化编程(Structured programming ) ”的开发方法的时候, 或许那个时代的人都会以为面向对象编程和面向对象的软件开发(OOP/OOSD)就是我们一直所追求的那颗能够搞定一切的“Silver Bullet ”, 但不得不承认的就是,基于面向对象的软件开发模式依然不能很好的解决软家开发中的所有问题。

软件开发的目的最终是为了解决各种需求,包括业务需求和系统需求, 使用面向对象方法,我们可以对业务需求等普通关注点进行很好的抽象和封装,并且使之模块化; 但对于系统需求一类关注点来说,情况却有所不同。

以笔者曾经经历的CREDIT项目为例吧,这是一个有关贷款业务的管理系统,从整个业务的角度来说, 我们提供了顾客申请贷款功能,顾客信息管理功能,贷款信息管理功能,贷款发放回收功能等等,这些都属于普通的业务需求, 通过面向对象方法,我们可以很容易按照功能划分模块并进行开发:

基本上,对于这种需求,需求与需求的具体实现之间的关系保持在1:1,你可以在系统中某一个确定的地点找到针对这种需求的实现, 无论从开发还是维护的角度,都比较方便。

不过,事情还没完那!开发中为了调试或者进入生产环境后为了对系统进行监控,我们需要对这些业务需求的实现对象添加日志记录功能; 或者,业务方法的执行需要一定的权限限制,那方法执行前肯定需要有相应的安全检查功能; 这些属于系统需求范畴,需求虽然都很明确,加入日志记录,加入安全检查,但是,要将这些需求以面向对象的方式实现并集成到整个的系统中去, 可就不是一个需求对应一个实现那么简单了。 对于系统中的每一个业务对象,都需要加入日志记录,加入相应的安全检查,那么,这些需求的实现代码就会遍布到所有需要这些需求的业务对象中:

图中Logging和Security需求和实现对应关系还仅仅是1:2,但随着系统中业务对象的增加,这个对应关系就会变成1:3,1:4...1:100甚至更多, 你可以想像一下,随着这个数目的增多,你的系统开发和维护的难度会向一个什么方向发展...

说白了,对于系统中普通的业务关注点,OOP可以很好的对其进行分解并使之模块化, 但却无法更好的避免类似于系统需求的实现在系统中的各处散落的问题。 所以,我们要寻求一种更好的方法,它或者在OOP的基础上更上一层楼,提出一套全新的方法论避免以上问题, 它也或者可以提供某种方法对基于OOP的开发模式做一个补足,帮助OOP以更好的方式解决以上问题。 但迄今为止,我们还找不到比OOP更加有效的软件开发方法,不过,好消息是,我们找到了后者,那就是AOP。

AOP全称为Aspect-Oriented Programming,中文通常翻译为“面向方面编程 ”, 使用AOP,我们可以对类似于Logging和Security等系统需求进行模块化的组织, 简化系统需求与实现之间的对比关系,进而使得整个系统的实现更具模块化。

 

任何一个软件系统就跟CREDIT系统一样,日志记录,安全检查,事务管理等等系统需求就像一把把刀“恶狠狠 ”的横切到我们组织良好的各个业务功能模块之上, 以AOP的行话来说,这些都是系统中的“横切关注点(cross-cutting concerns) ”,使用传统方法,我们无法更好的以模块化的方式对这些横切关注点进行组织和实现, 所以AOP引入了Aspect的概念,用来以模块化的形式对系统中的横切关注点进行封装。Aspect之对于AOP,就相当于Class只对于OOP,我们说过AOP仅仅是对OOP方法的一种补足, 当我们把以Class形式模块化的业务需求和以Aspect形式模块化的系统需求拼装到一起的时候,整个系统就算完成了。

3.1.1. AOP的尴尬

如果把我们的软件系统看作是可以划分为不同形状的积木的话,对于业务需求类型的积木块和系统需求类型的积木块来说,他们的形状和材质可以是相近甚至是相同的,但摆放的空间位置却完全处于不同的维度。

当对整个系统进行分析之后,我们可以将不同的需求实现为Aspect型的积木块或者Class类型的积木块,这样,我们就有了这么一盒积木:

不过,积木块永远是积木块,需要你动手搭建才能构建出美丽的模型,当我们把Class类型的积木块在一个空间面上摆放,而将Aspect类型的积木块在另一个空间面上摆放的时候,我们就有了一座美丽的城堡(立体的哦):

也就是说,OOP的空间结合AOP的空间就可以构建一个完美的系统。 不过,由于当前技术所限,我们虽然可以构造出AOP使用的各个积木块, 但我们却无法构建属于AOP的独有空间,这就像俄罗斯方块一样。 我们现在使用OOP进行软件开发,就好像我们在玩俄罗斯方块,当各个系统业务模块划分完成之后(俄罗斯方块就那几个特定的砖头形状), 剩下的工作就是在游戏规定的空间维度里想方设法的把每一层都填满,否则,你就得等着你的系统崩溃,彻底的game over。 还好,我们已经精熟于那几个砖头的方向调整和恰当的放置位置,就好像我们精熟于Design Pattern一样,但是,即使如此, 这并没有解决根本的问题,你还是时不时的遗漏某个位置的空间,而实际上,如果允许,最简单的方法,就是从你所处的维度,直接使用合适的砖头把那些遗漏的空 间填补上就算大功告成,你也不用太过于费尽脑汁的在那个有限的游戏空间内考虑如何使用原来的规则消去遗漏的空间块。

俄罗斯方块那个游戏空间就是我们的OOP现在的空间,而AOP应该在另一个空间内,才可以提供最大的便利,但是,如你所见,AOP现在没有主权,呵呵, 所以,现时代的AOP实现都需要"寄生"于OOP的主权领土中,系统的维度也依然保持曾经OOP持有的“维度世界纪录 ”。

3.1.2. AOP走向现实

AOP是一种理念,要实现这种理念通常需要一种现实的方式,与OOP需要相应的语言支持一样,AOP也需要某种语言以帮助实现相应的概念实体,我们统称这 些实现AOP的语言为AOL,即Aspect-Oriented Language,不要跟American On Line混淆哦,^_^

AO语言可以与系统实现语言相同,比如如果系统实现语言为Java,那相应的AO语言也可以为Java;但AO语言并非一定要与系统实现语言相同,它也可以是其他语言,比如AspectJ是扩展自Java的一种AO语言,显然与系统实现语言属于不同的两种语言。

除了主流的OOP语言,软件开发界现在已经有一些专门针对AOP的语言扩展,除了上面提到的对Java语言扩展后产生的AspectJ,还有:

  • AspectC

  • AspectC++

  • Aspect.Net

  • AspectL(Lisp)

  • AspectPHP

等等...

囿于现实中AOP技术实现上的尴尬,AO语言实现的AOP各个概念实体最终都需要某种方式集成到系统实现语言所实现的OOP实体组件中,所以,系统实现语言通常称为系统中使用的AO语言的“寄生语言 ”,而将AO组件集成到OO组件的过程,AOP中称之为织入(Weave)过程。

AOP的Aspect织入到OOP系统的过程实现方式千差万别,但不管如何实现,织入过程是处于AOP和OOP的开发过程之外的,而且对于整个系统的实现 是透明的,开发只需要关注相应的业务需求实现或者系统需求的实现即可。当所有业务需求和系统需求以模块化的形式开发完成之后,通过织入过程就可以将整个的 软件系统集成并付诸使用。

Java界的AOP框架或者说产品可谓AOP界的一朵奇葩,在Xerox公司的PARC(Paro Alto Research Center) 提出AOP的一套理论之后, Java界各种AOP框架就如雨后春笋般涌现, 其走过的路亦不可谓不精彩,所以,让我们来回顾一下这段精彩历史何如?

静态AOP时代

即第一代AOP,以最初的AspectJ 为杰出代表, 其特点是,相应的横切关注点以Aspect形式实现之后,会通过特定的编译器将实现后的Aspect编译并织入到系统的静态类中。 比如,AspectJ会使用ajc编译器将各个Aspect以java字节码的形式编译到系统的各个功能模块中,以此达到融合Aspect和Class的目的。 而像EJB所提供的声明性事务等AOP关注点的实现,也应该归入第一代AOP行列,只不过,所采用的实现机制不同,但特点是一样的(我们后面马上将提到java平台上实现AOP的各种机制)。

静态AOP的优点是,Aspect直接以java字节码的形式编译到java类中,java虚拟机可以像通常一样加载java类运行(因为编译完成的Aspect是完全符合java类的规范的), 不会对整个系统的运行造成任何的性能损失。

缺点嘛,就是灵活性不够。如果横切关注点需要改变织入到系统的位置,就需要重新修改Aspect定义文件,然后使用编译器重新编译Aspect并重新织入到系统中。

动态AOP时代

又称为第二代AOP,该时代的AOP实现大都通过Java语言提供的各种动态特性实现Aspect织入到当前系统的过程, 比如JBossAOP,SpringAOP以及Nanning等AOP框架,在AspectJ融合了AspectWerkz框架之后,也引入了动态织入的行为,从而成为现在Java界唯一同时支持静态AOP和动态AOP特性的AOP实现产品。

第二代AOP的AOL大都采用Java语言实现,AOP各种概念实体全部都是普通的Java类,所以很容易开发集成,Aspect跟Class一样最终以class身份作为系统的一等公民存在, 与静态AOP最大的不同就是,AOP的织入过程在系统运行开始之后进行,而不是预先编译到系统类中,而且织入信息大都采用外部XML文件格式保存, 可以在调整织入点以及织入逻辑单元的同时不必更动系统其他模块,甚至可以在系统运行的时候也可以动态更改织入逻辑。

但动态AOP在引入灵活性以及易用性的同时,性能问题则是一个不可回避的问题。 因为动态AOP的实现产品大都采用在类加载或者系统运行期间对系统字节码进行操作的方式来完成Aspect到系统的织入, 难免会造成一定的运行性能损失,但随着JVM版本的提升对反射以及字节码操作技术的更好支持,这样的性能损失在逐渐减少, 大多数情况下,这种损失是可以容忍的。

3.1.3. Java平台上的AOP实现机制(AOP Implementation Mechanism on Java Platform)

在Java平台上可以使用多种方式实现AOP,下面提到的几种方式是最经常使用的,而且也通过相应AOP产品的验证,他们可都是帮助我们的AOP在Java平台走向现实的基石啊!

3.1.3.1. 动态代理(Dynamic Proxy)

JDK1.3之后,引入了Dynamic Proxy机制, 可以为相应的Interface在运行期间动态生成相应的代理对象, 所以,我们可以将横切关注点逻辑封装到动态代理的InvocationHandler中, 然后在系统运行期间,根据横切关注点需要织入的模块位置,将横切逻辑织入到相应的代理类中。 以动态代理类为载体的横切逻辑现在当然就可以与系统其他实现模块“共创大业 ”了。

这种方式实现的唯一缺点或者说优点就是所有需要织入横切关注点逻辑的模块类都得实现相应的接口, 因为动态代理机制只针对接口有效。 当然,之前也说了,毕竟动态代理是在运行期间使用反射,相对于编译后的静态类的执行,性能上可能稍逊一些。

SpringAOP默认情况下采用这种机制实现AOP机能。哦,Nanning也是,这个框架是以我国的“南宁 ”市命名的,只支持动态代理机制。

3.1.3.2. 动态字节码增强(dynamic byte-code enhancement)

我们知道Java虚拟机加载的class文件都是符合一定规范的, 所以,只要交给Java虚拟机运行的文件符合class规范,程序的运行就没有问题。 通常的class文件都是我们从java文件使用javac编译器编译而成的, 但只要符合class规范,我们也可以使用asm 或者Cglib 等java工具库在程序运行期间动态构建字节码的class文件。

在这样的前提下,我们可以为需要织入横切逻辑的模块类在运行期间通过动态字节码增强技术,为这些系统模块类生成相应的子类, 而将横切逻辑加诸到这些子类中,让应用程序在执行期间实际上使用这些动态生成的子类,从而一样可以达到将横切逻辑织入系统的目的。

使用动态字节码增强技术,即使模块类没有实现相应的接口,我们依然可以对其进行扩展,而不用像动态代理那样受限制于接口。 不过,这种实现机制依然存在不足,如果需要扩展的类以及类中的实例方法等声明为final类型的话,则无法对其进行子类化的扩展。

SpringAOP在无法采用动态代理机制进行AOP功能扩展的时候,会使用Cglib库的动态字节码增强支持来实现AOP的功能扩展。

3.1.3.3. Java代码生成(Java Code Generation)

实际上,如果你从早期的J2EE开发走过来的话,或者具体点儿,如果你接触过早期的EJB2开发的话,你已经接触了这种类型的AOP实现。

 

  • 可否记得现在依然让人念念不忘的容器内声明性事务支持?

  • 可否还记得CMP类型的实体Bean只需要声明接口,而不用给出相应的接口实现类?

  • 又是否还记得大多数Applicaiton Server提供商都会提供特定的EJB部署工具以帮助你进行EJB的部署?

事务属于跨越整个系统的一种横切关注点,所以,EJB容器提供的声明性事务属于一种AOP模块功能实现。但早期大多EJB容器在实现这一功能的时候,会采用Java代码生成技术, 这就是你不需要提供CMP的接口实现类的原因,也是EJB容器提供商大多提供部署接口或者专有部署工具的原因。

EJB容器根据部署描述符文件提供的“织入 ”信息,会为相应的功能模块类根据描述符所提供的信息生成对应的Java代码, 然后通过部署工具或者部署接口编译Java代码生成相应的java类,之后,部署到EJB容器的功能模块类就可以正常工作啦。

这种方式比较“古老 ”了,也就早期的EJB容器使用最多,现在已经退休咯。

3.1.3.4. 自定义类加载器(Custom Classloader)

所有的Java程序class都要通过相应的类加载器加载到Java虚拟机之后才可以运行,默认的类加载器会读取class字节码文件, 然后按照class字节码规范解析并加载这些class文件到虚拟机运行,如果我们能够在这个class文件加载到虚拟机运行期间插入一脚,将横切逻辑织入到class文件的话,是不是就完成了AOP和OOP的融合那?

我们可以通过自定义类加载器的方式完成横切逻辑到系统的织入,自定义类加载器通过读取外部文件规定的织入规则和必要信息, 在加载class文件期间就可以将横切逻辑添加到系统模块类的现有逻辑中,然后将改动后的class交给Java虚拟机运行,oho,偷梁换柱的漂亮,不是吗?!

通过类加载器,我们基本可以对大部分类以及相应的实例进行织入,功能比之之前的几种方式当然强大很多,不过这种方式最大的问题就是类加载器本身的使用。 某些应用服务器会控制整个的类加载体系,所以,在这样的场景下使用可能造成一定的问题。

JBossAOP和已经并入AspectJ项目的AspectWerkz框架都是采用自定义类加载器的方式实现。

3.1.3.5. AO Language扩展(AOL extension)

这是最强大,也最难掌握的一种方式,我们之前提到的AspectJ就属于这种方式, 在这种方式中,AOP的各种概念在AO语言中大都有一一对应的实体,你可以使用扩展过的AOL实现任何AOP概念实体甚至OOP概念实体,比如Aspect以及Class。 所有的AOP概念在AOL中得到了最完美的表达。

采用扩展的AOL在AOP概念的表述上颇具实力,使得AOP牵扯的所有横切关注点逻辑在进行织入之前可以自由自在的存活在自己的“国度 ”中。 而且,具有强类型检查,基本可以对横切关注点要切入的系统运行时点有更全面的控制。 而像编译到静态类可以提升系统运行性能,Java虚拟机可以像加载平常类那样加载AO组件逻辑织入后的类文件并运行等特点,我们之前已经提过了。

不过,该方式强大的代价就是你需要重新学习一门扩展了旧有的语言的AOL或者全新的AOL,建议你在看完SpringAOP框架之后,再做出你的决定, 因为我们的观点一贯是K.I.S.S.(Keep It Simple Stupid).

分享到:
评论
3 楼 compiere 2009-08-04  

图怎么都挂了?
2 楼 fujohnwang 2009-06-24  

hantsy 写道
相对一门专门的 aop 语言,基于现有语言的aop方案更简单实用一些,如ejb的intecepter,aop appliance提供的相关接口。


合适的场景使用合适的工具,没有什么东西是普适的。
1 楼 hantsy 2009-06-23  
相对一门专门的 aop 语言,基于现有语言的aop方案更简单实用一些,如ejb的intecepter,aop appliance提供的相关接口。

相关推荐

Global site tag (gtag.js) - Google Analytics