Blog信息 |
blog名称: 日志总数:1304 评论数量:2242 留言数量:5 访问次数:7576382 建立时间:2006年5月29日 |

| |
[AOP][转]AOP及其Java实现机制 软件技术
lhwork 发表于 2006/9/25 14:27:36 |
1引言传统的编程技术,采用分解的方式将一个软件系统划分为相对较小的、易于分析、理解的模块,如果这些模块仍难以分析、理解,则迭代的将其分解
为更小的模块,直到这些模块可以容易的分析、理解、设计与编码。通过对这些模块进行分析,设计,然后使用相应的编程语言实现这些模块,再以编程语言所定义
的方式将这些模块组装起来,形成最终的软件系统。例如,面向过程编程中,将系统分解为完成单一功能的函数,并通过函数之间的调用完成复杂的功能,最终形成
这个系统。面向对象编程中,通过分析,抽象出一系列具有一定属性与行为的对象,并通过这些对象之间的协作完成系统的功能。传统的编程技术倾向于按
照功能或行为对软件系统进行分割,这个分割的结果是实现某一功能的模块,如函数、过程、类等。但在编程实践中,人们认识到,软件系统中有些行为无法封装在
单个的模块中,例如日志记录、事物处理、对上下文敏感的错误处理、性能优化等等。通常这些功能与行为不实现系统的业务功能,但辅助这些功能的实现,并散布
在实现系统功能的诸多模块中,从而造成代码的纠结,这使得实现系统功能的模块的代码难于阅读、理解、调试、维护和扩展等,并使得纠结的代码难于复用。如果
我们将实现系统业务功能的模块看作系统的纵向分解单元,那么上述分散在功能模块中的功能与行为就形成了一种横向的方面(Aspect),方面与模块形成了
横切(Crosscutting),从而造成传统的编程技术无法将方面模块化,造成两种代码纠结(Tangling)在一起。因此,我们需要一种新的编程思想,对系统中的横切方面进行处理,解决由此造成的代码纠结问题,并使方面可模块化,促进功能模块与方面彼此的复用。2 AOP简介面
向方面编程(Aspect-Oriented Programming,AOP)就是这样一种区别于传统编程技术的新的编程思想,最初由
Gregor Kiczales在施乐的Palo Alto研究中心领导的一个研究小组于1997年提出。AOP正是基于方面与模块形成横切,造成代码纠
结这一观察提出的,并提出了一种新的编程思路来解决这一问题。之后,AOP经由几个不同小组的努力在各自的方向上得到了发展,到目前为止,AOP仍处于发
展阶段。2.1 AOP的基本思想如前所述,造成代码纠结的原因在于传统编程技术中,软件系统中非业务功能实现的代码无法模块
化,散布在实现业务功能的代码中造成的。这里,我们引入关注点(Concern)的概念,关注点就是软件系统中需要解决的问题。软件系统的业务功能组成了
核心关注点(Core Concerns),也就是软件系统要解决的问题,而诸如日志记录,事物处理等关注点就形成了横切关注点
(Crosscutting Concerns),因为,这些关注点散布在核心关注点中,相互形成了横切的关系,横切关注点也就是前面提到的方面这一概
念。有鉴于此,AOP提出的解决方法是对这两种相互横切的关注点分别进行编码,使用传统的编程语言对核心关注点编程,使用面向方面的编程语言对横
切关注点,也就是方面进行编程。然后使用一种机制将这两种代码自动混合起来,形成最终的代码。在这里,面向方面的编程语言可以是已有编程语言的扩展
(AspectJ,AspectC++,AspectC,AspectC#,Apostle等),或是一种新的语言,设计用于编写方面的代码
(Caesar,D2AL,JasCo等)。而将两种代码混合的机制称为织入(Weaving),实现这一机制的工具称为织入器(Weaver)。因此,
对方面单独编码,并通过适当的织入机制使两种代码混合,构成了AOP解决代码纠结问题的基石。2.2 织入机制如前所述,织入是
实现AOP的一个重要机制,织入的实现机制有多种,基本上可以分为两类,静态织入与动态织入。静态织入是指在业务功能代码中的适当位置,比如某段代码执行
前,或执行后,将方面中的编码插入,从而形成混合的编码。方面中的编码在程序运行前,已被内联至业务功能代码中,因此,代码可以被优化,从而使织入产生的
开销最小化,最终产生的混合代码,其执行速度接近为使用AOP方式编写的代码。但是,静态织入无法做到在程序运行时,根据运行上下文动态的决定插入的方面
代码,动态织入则可以做到这一点。动态织入可以在程序运行时,根据上下文决定调用的方面,它们的先后顺序,增加或删除一个方面等。而根据织入的时
间,又可以分为三类,编译时,载入时,运行时。编译时织入可以在编译前进行预处理,将两种代码自动混合,将方面中的代码自动插入到功能模块代码的合适位置
处,也可在编译后,对编译后的代码进行操作。载入时织入是在代码载入时,实现代码的织入。运行时织入则在运行时,根据对方法的调用执行适当的方面代码以实
现织入。以AOP的Java实现为例,包括使用反射(Reflection),基于动态代理(Dynamic Proxy)或其它机制的拦截框架,基于元数据(Metadata)的操作,以及类载入时对字节码的操作等。随着AOP应用的进一步推广,相信会有更多新的织入方式出现。3 AOP实现机制根据来自于AOSD(Aspect-Oriented Software Development,http://aosd.net)的统计,目前与AOP相关的项目已达近百种,而基于Java的AOP实现机制也有二十多种,以下所列举的是商业上得到成熟应用的几种基于Java的AOP的实现机制。3.1 AspectJAspectJ
是目前最完善的AOP语言,由AOP的首倡者Gregor Kiczales领导的一个小组提出并得到发展。AspectJ是对Java编程语言的扩展,
通过增加了一些新的构造块支持对横切关注点的模块化封装,通过对源代码级别的代码混合实现织入,是一种典型的使用静态织入的AOP实现机制。
AspectJ提供了两种横切实现机制,一种称为动态横切(Dynamic Crosscutting),另一种称为静态横切
(Static Crosscutting)。动态横切是指在程序执行的某一个明确的点上运行额外的,预先定义好的实现,是一种静态实现机制,并
非是动态的。为了实现动态横切,AspectJ中引入了四个新的概念:连接点(Join Point),切入点(Pointcut),参考
(Advice)和方面(Aspect)。连接点是明确定义的程序执行过程中的一个点,切入点则是指一组相关的连接点,参考定义了在连接点执行的额外实
现,方面则是指对横切关注点的模块化封装实现的单元,类似于AOP中的类,由切入点,参考与普通的Java成员声明组成。如前所述,连接点是程序执行中明
确定义的点,比如,类接受到方法调用时,方法调用时,属性访问时都是连接点的例子,在连接点处可以执行预定义的额外实现。而要指明在哪些连接点上执行,则
需要定义切入点,切入点可以在程序运行时匹配特定的连接点,AspectJ中预定义了一系列标准切入点,包括方法与构造器的调用,接受调用,执行,域的
get,set访问,异常处理,实例类型匹配,处于类或方法体中,控制流中,调用者调用方法,类型的初始化与静态初始化,通过这些预定义切入点的组合可以
实现自定义的、复杂的切入点。在编译时,方面中的参考将被转化为标准的方法,类代码中匹配切入点的连接点将被转化为一个静态的标记点,然后,这些静态的点
将被对参考所转化成的方法的调用所取代,由此完成两种代码的织入,最后对织入完成的代码编译为字节码,即完成了整个编译过程。目前,AspectJ即支持
编译前的预处理方式实现代码的织入,也支持编译后的字节码操作。静态横切是指对已存在的类型定义引入新的方法,属性等,与动态横切不同,静态横切不改变类型的动态行为,而是改变其静态结构,也即导入(Introduction)。通过在方面代码中声明方法,属性,需要继承的超类,接口等,在代码织入时,可以改变应用此方面的类的定义。3.2 AspectWerkzAspectWerkz
是一个动态的AOP框架,利用对字节码的修改实现方面的织入,并使用Java虚拟机的动态替换字节码的能力实现动态AOP的要求。AspectWerkz
没有扩展Java语言,方面、参考、切入点等均使用标准的Java构造块,即类以及方法来实现,并使用XML文件定义这些构造块,此外
AspectWerkz还支持使用JavaDoc标记实现的运行期属性定义。AspectWerkz采用了与AspectJ相似的连接点模型,只是支持的
连接点种类少于AspectJ,参考的类型一致。AspectWerkz通过引入一个间接层,方面容器(Aspect Container)以及
对字节码的转化,即代码织入实现动态AOP的要求,方面容器管理部署好的类、方面代码,并根据XML文件或JavaDoc注释中定义的方面,参考,切入点
等得到连接点处相关的方面信息,并在程序的执行中控制执行流程,在匹配的连接点处执行适当的参考。AspectWerkz通过类载入层次的适当位
置拦截类载入从而实现字节码的修饰。AspectWerkz提供了两种织入模式实现AOP:静态织入以及动态织入。静态织入只在类载入时对字节码作一次性
的转化,通过将类的方法实现移入AspectWerkz命名的方法中,将原方法中的代码改写,由方面容器调用适当的参考,并调用前述
AspectWerkz添加的方法从而完成代码的织入。导入则由混合类型(Mixin)实现,用于为类增加新的方法,混合类型是一种使用接口与实现类的方
式模拟多重继承的机制。AspectWerkz通过修改字节码使被导入的类实现混合类型的接口,并在接口定义的方法中,将控制交给容器管理器,由它来完成
对实现的调用。静态织入可以在运行时动态的为切入点增加,删除参考,可以引入新的参考,但是无法定义新的切入点,这需要动态织入。动态织入由两阶段织入完
成,分别为类载入阶段与激活阶段。首先,在类载入时,按照静态织入的方法,为需要实现动态织入的类的每个方法添加一个相应的空的方法,匹配连接点的方法除
外。然后,在激活阶段,原方法体中的代码将被交换到类载入时新产生的方法中,原方法将实现静态织入时相同的处理,从而方面容器控制流程。前述代码交换是由
热交换(HotSwap)实现的,这是JVM提供的API。通过方面容器与织入模型,AspectWerkz提供了动态AOP的实现。 3.3 SpringFrameworkSpringFramework
是一个采用了反转控制(Inversion of Control, IoC)策略的基于J2EE的轻量级应用框架。SpringFramework的核
心是IoC容器,对于其它应用,如数据库访问,日志等,SpringFramework多使用现有的、成熟的框架。SpringFramework采用了
模块化的方式,各模块可以共同使用,也可以单独使用其中的一个模块, SpringFramework的一个模块提供了对动态AOP的支持,
SpringFramework中提供的声明式事务管理就是基于动态AOP的。SpringFramework中AOP的实现基于动态代理
(Dynamic Proxy), 动态代理源于代理模式,即通过接口实现对业务对象的访问,但动态代理无需为每一个需代理的业务对象静态的生成代理对
象,只需提供需要代理的接口与代理实现,就可以在运行时动态的生成代理对象,代理对上述接口的访问,同样的机制也使用于对类的代理,使用类似于修饰者的模
式,通过子类化实现。SpringFramework默认使用JDK提供的动态代理机制,此时,业务对象通过接口编程,若需要代理对类的访问,则需要使用
CGLIB,这是一个开源的动态代理实现。SpringFramework的AOP实现不同于AspectJ与AspectWerkz,它不是完
全的AOP实现,而是设计用于在应用服务器环境下实现AOP,与SpringFramework的IoC容器配合使用。SpringFramework中
参考,切入点与方面均由普通Java对象实现,其中连接点模型与AspectJ相同,只是远不如AspectJ丰富,目前只提供对方法调用的拦截。有4种
类型的参考,分别为方法调用时,之前,返回时与抛出异常时,通过实现SpringFramework的参考接口可以自定义参考类型。在
SpringFramework中,方面称为Advisor,是一个包含参考与切入点的Java类。像其它由IoC容器管理的组件一样,参考,切入点与方
面也由IoC容器管理 ,由XML配置文件定义。配置的内容包括业务对象的接口与实现,自定义的或由SpringFramework提供的切入点与参考
类,或使用Adviser类取代单独的切入点与参考类。在运行时,通过IoC容器进行名称查找,就可以由容器使用代理机制自动产生代理对象,并在符合切入
点定义的连接点处执行参考。SpringFramework除自身实现的AOP框架外,还在寻求与其它AOP实现机制的整合,目前已经实现了与
AspectJ的整合,以利用AspectJ丰富的切入点语法,并利用AspectJ的方面实现。3.4 JBossJBoss
是一个开源的符合J2EE规范的应用服务器,作为J2EE规范的补充,Jboss中引入了AOP框架,为普通Java类提供了J2EE服务,而无需遵循
EJB规范。Jboss通过类载入时,使用Javassist对字节码操作实现动态AOP框架,Javassist是一个开源的编辑字节码的类库。Jboss
中参考,切入点与方面也由普通Java对象实现,并使用XML文件配置。Jboss的连接点模型与AspectJ略有不同,提供了一系列预定义的切入点,
包括类匹配,方法调用,构造器调用,域访问,特定的调用与被调用关系。通过这些切入点的逻辑运算,可以实现更为复杂的切入点。方面为Java类,参考是其
中的一个方法,方面中不含切入点,方面主要为各种拦截器(Interceptor),拦截器即为只含一个参考的方面,单一连接点上可由多个拦截器形成拦截
器链,拦截器执行额外的操作。对方法的拦截由Advisor类管理,在连接点依次调用拦截器,并最终调用被逻辑的方法。而关于切入点,参考已及方面的信息
由AspectManager管理。此外,Jboss提供对元数据的支持,用于为类,方法,构造器以及域添加额外的属性,并可在运行期访问。为实
现拦截,Jboss需要修改类的字节码,大致过程如下。XML配置文件中关于切入点,拦截器,元数据以及混合类的信息在应用程序部署时被读入、解析,并生
成相应的对象,这些信息与实例化的对象由AspectManager管理。在需要混入方面代码的类载入时,AspectManager将创建
Advisor类,将方面相关信息传递给它,并对类的字节码进行修改,之后将修改过的字节码交给类载入器完成类的装载。字节码的修改主要是对被载入的类添
加一系列方法用于代理那些匹配连接点的方法调用,构造器调用,域访问以及方法导入,转为对Advisor类相应方法的调用。类中各方法将重命名,保留原方
法体,并添加一个与原方法同名的方法,在这个方法中调用那些代理方法,用来将调用代理给Advisor类,或调用重命名的原方法。对于域访问,分别添加两
个方法,对应于读与写操作,将域访问代理至Advisor类,在访问这个域的类中,则需将对域的访问转换为对上述方法的调用。对于构造器调用,则添加一个
方法,将调用代理至Advisor类,并对构造对象的类的构造代码作相应转换。对于导入,被导入的类中将添加一个混合类实现的引用,并添加混合类接口中的
方法,将对混合类方法的调用代理至Advisor类,并最终调用混合类的实现。相关类载入后,初始化Advisor类,填入拦截器链,以完成整个处理过
程。4结束语AOP不是一种取代传统编程技术的技术,而是对于这些技术的补充,它解决了一些传统技术无法很好解决的问题。特别是
对于面向对象技术,能起到相互补充的作用,解决横切关注分散在核心代码中,无法模块化的问题,对软件系统中不同的关注点进行分离。虽然,目前AOP仍处于
发展阶段,但已经出现众多的实际应用与学术研究项目,为AOP积累了一定的理论基础,出现了一些公认的基本要素。可以预见,现在的AOP必将像20年前的
OOP一样为软件开发带来变革。参考文献1 Kiczales, G., Lamping, J., Mendhekar, A., Maeda, C., Videira Lopes, C., Loingtier, J.-M., and Irwin, J. Aspect-Oriented Programming. In Proc. of ECOOP, Springer-Verlag (1997).2 Kai Bollert. On Weaving Aspects. In Proc. Of ECOOP, Springer-Verlag (1999). 3 Dynamic Weaving for Aspect-Oriented Programming. A. Popovici, T. Gross, and G. Alonso. Proceedings of AOSD, Enschede, The Netherlands (2002).4 Gregor Kiczales, Erik Hilsdale, Jim Hugunin, Mik Kersten, Jeffrey Palm and William G. Griswold. An Overview of AspectJ. In Proc. of ECOOP, Springer-Verlag (2001).5. Alexandre Vasseur. Dynamic AOP and Runtime Weaving for Java - How does AspectWerkz Address it? In Dynamic AOP Workshop of AOSD 20046. Jonas Boner. AspectWerkz ? Dynamic AOP for Java. http://aspectwerkz.codehaus.org/papers_talks.html7. Rod Johnson, etc. Spring Reference Documentation. http://www.springframework.org/documentation.html8. Jboss AOP Reference Guide. http://docs.jboss.org/aop/aspect-framework/reference/en/pdf/jbossaop_reference.pdf |
|
|