摘要
方面(Aspects)使得我们能够横切模块代码,在典型的面向对象应用程序中,这类代码有着公共的目的,但是被实现在分散的代码中,施乐公司帕洛阿尔托研究中心的AspectJ是由一群热情而经验丰富的用户和开发人员开发的一个开放源码的项目,他对java语言提供了一些简单的扩展,从而在java中实现了方面(Aspects),在这篇文章中,作者通过Sun[tm] ONE Studio 的AspectJ模块用一个简单的JavaBean[tm]例子来演示面向方面编程。
作者:Vaughn Spurlin
什么是方面(Aspects)
方面是程序语言概念,类似于类,但是它是更高一级的抽象,方面很干净的封装了一类关注点,这类关注点可能影响很多类但是却很难或不可能萃取成一个单独的类。一个简单的例子就是方法被调用时记录信息,不必查找庞大的代码体手工的插入信息记录代码到许多类中,方面将要用通配符表达式描述插入点,加上一行简单的记录信息代码。在通常意义上,AspectJ编译器不是源代码预处理器,并不直接插入java代码到源代码中,而是编译方面产生可执行代码,并在字节码级被织入,而不是源代码级。
方面不同于类,需要花时间去学习怎样有效的创建和使用方面,就象需要花时间去学习怎样创建和使用类,幸运的是,对于一个具有面向对象经验的开发者来说,简单的面向方面编程会很快学会并立即收益。
认识到方面的全部价值需要对它有非常深入的了解,可以通过渐进的方式获得,起初,可以在标准的java代码中加入一些临时的方面去帮助开发、测试和调试,后面,方面会被作为持久的代码加入到源码中,使得源码更加简略、更加干净、更加模块化,更容易理解,更安全的修改。
AspectJ是个开发工具,它扩展了java语言,加入了面向方面的性能, 一个AspectJ程序去除方面的定义,能够转化成一个有效的java程序,编译AspectJ源码产生能在任何有效的java虚拟机运行的标准java字节码。AspectJ有自己的编译器,但它没有提供一个完整的AspectJ环境, 而是用已安装的 java 2编译器产生可执行的字节码。这篇文章将贯穿一个例子,介绍一些面向方面的性能, 首先我们来看一些基本定义:
联结点(Join Point)
联结点是java 程序执行中的一个点,像我们上面所提到的“插入点”,联结点包括以下几种:
§ 调用 – 在方法被调用时的那个点
§ 执行 – 方法被调用后开始执行的那个点
§ 赋值 – 当一个非私有域被赋值
切入点(Pointcut)
切入点本质上是定义在一组联结点上的语法,它能在方面中被命名和使用,连接点是匿名的并且从来不会被单独指向,而是仅仅做为切入点的成员,一个详细的切入点在源码内部的任何联结点不是真就是假。
通知声明(Advice Declaration)
通知声明由“通知类型”后跟一个切入点和一段可执行的“通知体”组成,当java 程序控制流程通过切入点是真的联结点传递时,通知体被执行,通知类型真正决定了执行什么时候发生,例如,如果通知类型是“after()”并且切入点指定到某些方法的调用,那么在那些方法调用返回之后通知体被执行,被执行的通知体码在上面所提到的字节级被插入。
内部类型成员声明(Inter-Type Member Declaration)
内部类型成员声明是为一个类从外部增加成员而不修改类的一种机制,这使得增加一些和类的主要目的没有关系的成员和方法成为可能。
方面(Aspect)
方面是一个最高层结构,可以包括切入点、通知声明、内部类型成员声明和其它有效的java类成员声明,方面和类定义在同一级,是面向方面编程的基本模块单元。
简单例子
为了下面的例子安装AspectJ并不是必需的,你可以首先浏览一下例子,然后,如果你愿意,你可以安装AspectJ去编译和运行例子。例子包含下面的文件,单击文件名去在浏览器窗口中浏览源代码(或者下载它的压缩 demo-aspect.zip http://forte.sun.com/ffj/articles/aspectJ/demo-aspect.zip )。
GiveGreeting.java – 从bean中取得和显示问候 HelloBean.java – 支持“hello”问候的bean GoodbyeBean.java –支持“good-bye”问候的bean Logger.java – 记录bean方法调用的方面 Counter.java – 统计getGreeting方法调用的方面 no_aspect.lst – 不用方面的AspectJ建立列表 logging.lst – 带有Logger方面的AspectJ建立列表 counting.lst –带有Logger和Counter方面的AspectJ建立列表
不用方面的例子
GiveGreeting.java、 HelloBean.java和GoodbyeBean.java是普通的java程序,如果用正常的方式,不用AspectJ编译, GiveGreeting.java能被执行并产生下面的结果,图1显示了GiveGreeting不用方面的结果。
图1:不用方面的输出结果
用no_aspect.lst建立列表在AspectJ中编译,会产生同样的结果,因为在no_aspect.lst中没有包含方面文件。
带有Logger方面的例子
如果程序被AspectJ用logging.lst建立列表编译,logger.java方面被包括,执行GiveGreeting产生如图2所示的结果,这个例子是面向方面编程的第一步,用一个临时的方面帮助开发。
图2:带有logging方面的输出结果
注意,对于get和set方法的每次调用,都将触发记录信息,而且bean中的方法被调用信息也将被记录。检查Logger.java代码(在下面)来看它是怎么工作的:
aspect Logger {
pointcut log():
execution(* *.getGreeting(..)) || execution(* *.setGreeting(..));
before() : log() {
System.out.println(" Logger: " +
thisJoinPoint.getSignature());
}
}
首先定义一个名字为log的切入点,组成它的是任何命名为getGreeting和setGreeting的方法执行开始的所有联结点,这些方法带有任意的参数,注意通配符“*”的用处是为方法指定访问修饰符和类名,替代方法参数列表的“. .”是个通配符,意思是任何类型任何数量的参数。切入点由下面部分组成:
关键字: pointcut 切入点标识符; log() – 使的切入点能被用在任何需要的地方的标识符 切入点声明: execution(* *.getGreeting(..)) || execution(* *.setGreeting(..)); – 对于属于这个切入点的联结点,条件必须为真
接下来,声明了一个通知,它会在由log切入点所标示的任何方法调用之前执行,当通知执行时,它将打印记录信息,包括触发通知的方法签名。通知由下面部分组成:
通知类型: before() – 这种通知类型在属于切入点的任何联结点之前执行通知体 切入点: log() – 这是一个命名的切入点引用, 这里使用基本的切入点也是合法的 通知体: { System.out.println(" Logger: " + thisJoinPoint.getSignature()); } – 这个通知体打印 "Logger:" 加上满足切入点条件的方法签名
带有Count方面的例子
如果程序被AspectJ用counting.lst建立列表编译,Counter.java方面被加入(看图3), 执行GiveGreeting产生如下结果:
图3:带有Counting方面的输出结果
这个例子使用了内部类型成员声明,演示了横切关注点的模块化,作为持久的代码加入到源码中。
除了记录信息,现在输出也包括计算信息来告诉getGreeting方法已经被调用的次数。我们来看一下Counter.java中的内部类型成员声明:
aspect Counter {
private int HelloBean.countGetGreeting = 0;
private int GoodbyeBean.countGetGreeting = 0;
declare parents : GoodbyeBean extends HelloBean;
pointcut count(HelloBean h): call(* *.getGreeting(..)) && target(h);
after(HelloBean h) : count(h) {
h.countGetGreeting++;
System.out.println(" Counter: " +
h.countGetGreeting + " calls to " +
thisJoinPoint.getSignature());
}
}
在两个bean中声明整型域countGetGreeting,GoodbyeBean被声明为HelloBean的继承,如果extends语句在java的类定义中是合法的继承语句,那么在AspectJ中声明一个父子关系也是合法的。CountGetGreetin声明和父子关系声明是内部类型成员声明的例子。
内部类型成员声明是强大的模块化横切关注点工具,在这个例子中,所有有关getGreeting方法调用次数的计算都包括在Counter方面中,如果没有方面,必须加入countGetGreeting域到bean类定义中,将计算的逻辑与bean中和计算无关的逻辑混合在一起,负责计算的开发人员需要去查找分散在几个类中和非计算代码混在一起的计算逻辑。
接下来的内部类型成员声明是一个名字为count的切入点,它包括了名字为getGreeting的方法被调用时的所有联结点,方法必须在HelloBean类中或HelloBean的子类中,注意这个切入点用的是形参,HelloBean h指明它只应用在HelloBean类或HelloBean的子类的实例中。
在Counter方面中最大的代码块是一个通知,它在方法调用返回后执行,通知首先增加bean中的countGetGreeting,然后打印一个信息,像切入点count一样,通知用了一个形参HelloBean h,访问HelloBean实例。
在Sun[tm] ONE Studio中使用AspectJ
前面我们讨论了一个简单的例子,显示了java中方面的强大和简易,下面安装AspectJ最新版本获得一些实践经验。
Ø 下载并安装AspectJ
AspectJ可以从AspectJ网站下载页获得(http://aspectj.org/servlets/AJSite?channel=download&subChannel=compilerAndTools), 每个下载都打包为JAR文件,执行JAR文件将启动安装。
下载安装下面表中你所选择的部件
Component
Comments
ajde-forteModule-1.0.6.jar
需要
Forte[tm]的AspectJ开发环境,Sun ONE Studio 或NetBeans[tm]的AspectJ模块
aspectj-tools-1.0.6.jar
可选
编译器和核心工具 – AspectJ编译器和命令行调试器。 在Sun ONE Studio使用AspectJ,编译器和核心工具并不是必需,但是它有助于在Sun ONE Studio之外使用AspectJ。
aspectj-docs-1.0.6.jar
或
aspectj-docs-1.0.6.tgz
两个可选
完整的文档、指南和例子。AspectJ文档也可以在线使用,本地安装只是为了方便。
注意:当ajde-forteModule-1.0.6.jar被执行去安装AspectJ开发环境时,集成开发环境不应该在运行状态。
当安装器提示选择安装路径时,选择集成开发环境的modules路径,安装AspectJ将使得Sun ONE Studio主窗口增加一个工具条,并在Tools菜单中增加一个子菜单。Ø 下载并安装例子
下面在Sun ONE Studio中安装AspectJ例子:
下载demo-aspect.zip (http://forte.sun.com/ffj/articles/aspectJ/demo-aspect.zip) 解压到工作目录 启动Sun ONE Studio 设置包含greetings java包的目录为工作目录
Ø 编译并执行例子
在AspectJ工具条上(看图4),点击第一个按钮(从左数),启动AspectJ开发环境,在任何时候再次点击这个按钮,将停止或再次启动AspectJ开发环境。
图4:AspectJ工具条
点击在AspectJ工具条上的第三个按钮,可以下拉出greetings例子的建立配置。 在建立配置下拉列表中,选择 <working-directory>/greetings/no_aspect.lst (看图5),去编译不带方面的java类。图5:建立配置下拉列表
当编译完成时,点击管理器窗口的Filesystems页,展开greetings包的内容(看图6),执行GiveGreeting产生上面不带方面例子的结果。greetings包显示在管理器中
重复过程,首先选择logging.lst产生如上所示带有logging方面例子的结果,然后选择counting.lst产生如上所示带有counting方面例子的结果。
这演示了编译方面的标准过程,这个过程由AspectJ建立列表控制,它指定了建立在一起的java类和方面。建立列表也可以用于AspectJ命令行工具的参数文件,AspectJ建立列表是手工创建的。
执行由方面建立的类的标准过程和执行任何java类的标准过程一样简单,面向方面的性能已经作为字节码和已编译过的类融为一体,执行它们没有什么特殊需求。
浏览方面
当AspectJ开发环境运行时,一个AspectJ页出现在管理器窗口中,点击页访问AspectJ浏览页,如图7所示。方面浏览器是个强大的工具,用来标识程序中被方面影响的元素。
图7:访问方面浏览器
调试方面
AspectJ执行时类必须配置使得它们对调试器可见。
在管理器中右键单击Filesystems结点 按着标准过程去设置<IDE_HOME>/lib/ext/aspectjrt.jar 设置断点进行正常的调试
当调试器执行到方面中一行代码的而不是java类中,方面在代码编辑窗口中被打开,调试像在一个正常的java类中一样进行。
如果在Sun ONE Studio中的NetBeans调试器完全支持JSR-45,调试会工作的很好,已经知道在一些代码行断点设置会存在问题 , 不过作者的程序都工作的很好,看AspectJ网站的常见问题解答会得到最新的信息。
方面编程导引
看AspectJ编程指南会了解语言的语义和语法、例子、术语和缺陷。术语是AspectJ摘录,主要是切入点。 缺陷是产生预期外结果的AspectJ程序,例如消耗内存直到溢出的死循环,这种问题一旦知道原因很容易解决,术语和缺陷章节是短小但很值得读的。
Aspect项目
“好的”, 你可能想,“我已经看到了面向方面编程的潜力,但是我真的敢用版本为1.0的工具去开发关键的系统么?”当语言和工具仍旧在发展,更新被增加的同时,要努力去向后兼容,更新是被用户需求驱动,用户需求保护了他们的程序投资。
万一有人集成方面到他们的持久代码中,但是被迫停止使用AspectJ,有一个退路,这种选择并不一定是必需的,但是谨慎的计划需要考虑最坏的情况。
这是捕获AspectJ产生的中间代码的一种简单方式:
点击AspectJ最右边的按钮(看图4),将打开AspectJ开发环境设置对话框将打开。 在“ajc Options”下选上Only preprocess and generate java source files项。 进入工作目录(Working Directory)中的目录:textfield.
现在在AspectJ下编译将产生在指定目录的有效java代码,产生的代码能被标准的java编译器编译。执行编译后的类将产生和执行被AspectJ编译后的类同样正确的结果。
迁移到面向方面编程(AOP)
AspectJ编程导引不仅是个参考,也是编程向导,作为一个简单的开始,通读编程导引,然后用方面去调试、测试和性能调整,当你变得更加有经验时,你可以继续前进,强制自己遵循编程标准、政策和约定,应用面向方面编程(AOP)到已存在的程序中,考虑用Aspect Mining tool工具去确定程序中潜在的横切关系。
面向方面编程(AOP)在编程中并没有很深的概念,只需付出一点努力,基本的功能就足够产生直接的收益,这不仅满足了快步伐的极限编程方格,也满足了每步在进行下一步之前需要收益证明的保守风格,完全采用面向方面编程(AOP)的价值和从结构化编程到面向对象编程一样有深远的意义,并且这种渐进的迁移几乎不费吹灰之力。
关于作者
Vaughn Spurlin 1967年在建立曾经最大的计算机的SAGE开始编程生涯,从1875年开始,他作为自由顾问,工作在计算机硬件和语言领域里,包括早期一些非常出名的个人计算机,现在Vaughn 在为Sun ONE Studio写一些技术文章和开发培训资料,并合作写了一本关于NetBeans的书。
本文地址:http://com.8s8s.com/it/it18101.htm