一AOP编程概览
面向对象编程技术进入软件开发的主流对软件的开发方式产生了极大的影响开发者可以用一组实体以及这些实体之间的关系将系统形象地表示出来这使得他们能够设计出规模更大更复杂的系统开发周期也比以前更短OO开发的唯一问题是它本质上是静态的需求的细微变化就可能对开发进度造成重大影响
AspectOriented Programming(AOP)是对OO技术的补充和完善它允许开发者动态地修改静态的OO模型构造出一个能够不断增长以满足新增需求的系统就象现实世界中的对象会在其生命周期中不断改变自身应用程序也可以在发展中拥有新的功能
例如许多人想必有过在开发简单的Web应用时将Servlet作为入口点的经验即用Servlet接收HTML表单的输入经过处理后返回给用户开始时的Servlet可能是非常简单的只有刚好满足用户需求的最少量的代码然而随着第二需求的实现例如实现异常处理安全日志等功能代码的体积就会增加到原来的三四倍——之所以称之为第二需求是因为Servlet的基本功能是接受和处理用户的请求对于这个目标来说日志安全之类的机制并不是必不可少的
AOP允许动态地改变OO的静态模型不必修改原来的静态模型也可以加入满足第二需求所需的代码(实际上甚至连原来的源代码也不需要)更令人称奇的是后来加入的代码往往可以集中在一个地方而不必象单纯使用OO时那样将后来加入的代码分散到整个模型
二基本术语
在介绍AOP开发实例之前我们先来了解几个标准的AOP术语以便更好地掌握相关的概念
█ Crosscutting concern
在OO模型中虽然大部份的类只有单一的特定的功能但它们通常会与其他类有着共同的第二需求例如当线程进入或离开某个方法时我们可能既要在数据访问层的类中记录日志又要在UI层的类中记录日志虽然每个类的基本功能极然不同但用来满足第二需求的代码却基本相同
█ Advice
它是指想要应用到现有模型的附加代码在本例中它是指线程进入或退出某个方法时要运行的日志代码
█ Pointcut
这个术语是指应用程序中的一个执行点在这个执行点上需要采用前面的crosscutting concern在本例中当线程进入一个方法时出现一个Pointcut当线程离开方法时又出现另一个Pointcut
█ Aspect
Pointcut和advice结合在一起就叫做aspect在下面的例子中我们通过定义一个pointcut并给予适当的advice加入了一个日志(logging)aspect
AOP还有其它许多特性和术语例如引入(Introduction)即把接口/方法/域引入到现有的类——它极大地拓宽了开发者的想象力不过本文只介绍一些最基本的持性熟悉这里介绍的概念后你再深入一步研究AOP的其它特性看看如何在自己的开发环境中使用它们
三现有的框架
目前最成熟功能最丰富的AOP框架当数AspectJAspectJ已成为大多数其它框架跟从的标准但是AspectJ也走出了非同寻常的一步它的实现为Java语言增添了新的关键词虽然新的语法并不难学但却意味着我们必须换一个编译器还要重新配制编辑器只有这样才能适应新的语法在规模较大的开发组中这些要求可能难以办到因为整个开发小组都会受到影响由于语言本身的变化开发小组把AOP技术引入到现有项目的学习周期随之延长
现在我们需要的是这样一个框架它可以方便地引入且不会对原来的开发和构造过程产生任何影响满足这些要求的框架不止一个例如JBoss AOPNanningAspectwerkz(AW)本文选用的是Aspectwerkz因为它可能是最容易学习的框架也是最容易集成到现有项目的框架
Aspectwerkz由Jonas Boner和Alexandre Vasseur创建它是目前最快速功能最丰富的框架之一虽然它还缺乏AspectJ的某些功能但己足以满足大多数开发者在许多情形下的需要
Aspectwerkz最令人感兴趣的特性之一是它能够以两种不同的模式运行联机模式和脱机模式在联机模式下AW直接干预属于JVM的底层类装入机制截取所有的类装入请求对字节码实施即时转换AW提供了干预类装入过程的许多选项另外还有一个替代bin/java命令的封装脚本这个脚本能够根据Java版本和JVM能力自动生成一组可运行的配制对于开发者联机模式有许多优点它能插入到任何类装入器并在类装入期间生成新的类也就是说我们不必手工修改应用程序的类只要按通常的方式部署即可不过联机模式要求对应用服务器进行额外的配制有时这一要求可能很难满足
在脱机模式下生成类需要二个步骤第一步是用标准的编译器编译第二步是重点——以脱机模式运行AWcompiler编译器让它处理新生成的类编译器将修改这些类的字节码根据一个XML文件的定义在适当的pointcut插入advice脱机模式的优点是AWcompiler生成的类能够在任何JVM 以上的虚拟机运行本文下面要用的就是这种模式因为它不需要对Tomcat作任何修改只要对构造过程稍作修改就可以照搬到大多数现有的项目
四安装
本文将以一个简单的Web应用程序为例它用Ant编译部署在Tomcat + Servlet容器上下面我们假定读者己准备好上述环境包括JVM +同时Tomcat被设置成从webapps文件夹自动部署应用自动将WAR扩展到目录(这是Tomcat默认的操作方式因此只要你尚未修改Tomcat的运行方式下面的范例可直接运行)我们将把Tomcat的安装位置称为%TOMCAT_HOME%
⑴ 从下载Aspectwerkz解开压缩到适当的位置我们将把这个位置称为%ASPECTWERKZ_HOME%
⑵ 设置%ASPECTWERKZ_HOME%环境变量
⑶ 将Aspectwerkz加入到PATH环境变量即设置set PATH=%PATH%;%ASPECTWERKZ_HOME%\bin\aspectwerkz
⑷ 下载本文的示范程序将它放入%TOMCAT_HOME%\webapps文件夹
⑸ 将Aspectwerkz的运行时类加入到Tomcat的classpath你可以将它的JAR文件放入示例应用的WEBINF\lib文件夹或放入%TOMCAT_HOME%\common\lib
五编译示例应用
如果你想深入研究一下本文的示例应用可以解开WAR文件提取它的内容你会发现根目录下有一个aspectwerkzxml文件构造应用时它会被复制到WEBINF/classes目录Servlet和advice的源文件在WEBINF/src目录下另外还有一个构建这些类的ANT脚本
在运行这个示例程序之前你还要对它进行后期编译下面是具体的操作步骤
⑴ 在命令行窗口中转到解开WAR文件的目录
⑵ 输入下面的命令调用AW编译器:aspectwerkz offline aspectwerkzxml WEBINF/classes cp %TOMCAT_HOME%\common\lib\servletjar如后期编译顺利通过应看到下面的输出
( s )
SUCCESS: WEBINF\classes
在构建文件中有一个名称为war的ANT任务你可以用它重新创建WAR文件
六运行示例应用
首先启动(或重新启动)Tomcat然后在浏览器中打//localhost:/demo/
页面打开后可以看到一个带二个输入框的HTML表单一个输入名字一个输入邮件地址输入一些数据然后点击按钮提交表单出现一个页面显示出联系人信息和一个指向联系人清单的链接
七代码分析
JSP页面就不分析了现在我们对它不感兴趣我们来看看AOPServlet的代码
package example;
import javaio*;
import javaxservlet*;
import javaxservlethttp*;
public class AOPServlet extends HttpServlet {
public void doGet(HttpServletRequest request HttpServletResponse response)
throws ServletException IOException {
Person person = new Person();
if (requestgetParameter(name) != null) {
personsetName(
requestgetParameter(name));
}
if (requestgetParameter(email) != null) {
personsetEmail(
requestgetParameter(email));
}
requestsetAttribute(person person);
RequestDispatcher rd =requestgetRequestDispatcher(/viewjsp);
rdforward(request response);
}
}
在这个例子中Servlet的代码己尽量精简只包含一些必不可少的代码如创建了一个绑定请求参数的对象等但没有持久化操作不需要额外的imports它只实现了作为Servlet必须实现的最基本的操作
然而根据说明文档的要求这个应用程序必须将所有Person类型的对象特久化所以要为这个应用程序加入一个aspect为创建这个aspect我们首先要创建一个aspectwerkzxml文件并将该文件放入classpath指定的目录本文示例提供了一个简单的例子你可以用编辑器打开查看
aspectwerkzxml的第一部份定义了可用的advice我们可以根据需要加入任意数量的advice
<advicedef name=persist class=examplePersistenceAdvice deploymentmodel=perJVM/>
在这个片段中我们定义了一个名称为persist的advice它的类型是examplePersistenceAdvice最后一个属性定义了该advice的排它性在这里它的值是perJVM