AspectJ 给 Java 语言带来了 AOP
http://www.ibm.com/developerworks/cn/java/j-aspectj/index.html
使用面向 Aspect 的编程改进模块性AspectJ 给 Java 语言带来了 AOP |
级别: 初级 Nicholas Lesiecki (ndlesiecki@apache.org), 技术团队领导, eBlox, Inc 2002 年 1 月 07 日 面向 Aspect 的编程(AOP)是一种新的编程技术,它允许程序员对 横切关系(crosscutting concerns)(跨越典型职责界限的行为,例如日志记录)进行模块化。AOP 引进了 Aspect,它将影响多个类的行为封装到一个可重用模块中。使用 Xerox PARC 的 AspectJ 最新发行版,Java 开发人员现在可以利用 AOP 能够提供的模块化。本文介绍了 AspectJ 并说明了使用它所带来的设计益处。请通过点击本文顶部或底部的 讨论,在 论坛中与作者以及其他读者分享您的想法。 人们认识到,传统的程序经常表现出一些不能自然地适合单个程序模块或者几个紧密相关的程序模块的行为,因此面向 Aspect 的编程(AOP)应运而生。Aspect 的先驱将这种行为称为 横切,因为它跨越了给定编程模型中的典型职责界限。例如,在面向对象的编程中,模块性的天然单位是类,横切关系是一种跨越多个类的关系。典型横切关系包括日志记录、对上下文敏感的错误处理、性能优化以及设计模式。 如果使用过用于横切关系的代码,您就会知道缺乏模块性所带来的问题。因为横切行为的实现是分散的,开发人员发现这种行为难以作逻辑思维、实现和更改。例如,用于日志记录的代码和主要用于其它职责的代码缠绕在一起。根据所解决的问题的复杂程度和作用域的不同,所引起的混乱可大可小。更改一个应用程序的日志记录策略可能涉及数百次编辑 ― 即使可行,这也是个令人头疼的任务。与之形成对比的是面向 Aspect 的编程的基本案例之一。在标题为“ Aspect-Oriented Programming”的文章中,一些 AspectJ 作者谈到了性能优化,传统技术可以将程序从 768 行扩充到 35,213 行。用面向 Aspect 技术重写后,代码缩回到 1,039 行,并保持了大多数性能优点。 AOP 通过促进另一种模块性补充了面向对象的编程,该模块性将横切关系广泛分布的实现聚拢到一个单元。这种单元称为 Aspect,这就是名称面向 Aspect 的编程的来历。通过划分 Aspect 代码,横切关系变得容易处理。可以在编译时更改、插入或除去系统的 Aspect,甚至重用系统的 Aspect。 为了获得对面向 Aspect 的编程更好的感性认识,让我们看一看来自 Xerox PARC 的面向 Aspect 的 Java 编程语言扩展 ― AspectJ。在示例中,将使用 AspectJ 来做日志记录。示例是从开放源码 Cactus 框架中抽取出来的,它简化了服务器端 Java 组件的测试。框架的贡献者决定通过对框架内的所有方法调用进行跟踪来辅助调试。Cactus 的 1.2 版本没有使用 AspectJ 编写,典型方法如下列清单 1 中所示: 清单 1. 日志调用手工插入到每个方法中
作为该项目代码约定的一部分,要求每个开发人员将这几行代码插入到他或她所编写的任何方法中。还要求开发人员记录每个方法的参数。如果没有对部分项目监督进行详尽的代码复查,就很难履行这种约定。在 1.2 版本中,大约有 80 个单独的记录日志调用分布在 15 个类中。在框架的 1.3 版本中,用单一 Aspect 代替了这 80 多个调用,该 Aspect 自动记录参数和返回值以及方法入口和出口。清单 2 包含了这个 Aspect 的一个作了很大简化的版本(例如,我省略了参数和返回值日志记录)。 清单 2. 自动应用于每个方法的记录日志调用
让我们分解示例并研究 Aspect 做了什么。您将注意到的第一件事是 Aspect 的声明。Aspect 声明类似于类声明,它们都定义 Java 类型,就象类所做的那样。除了其声明以外,该 Aspect 还包含 Pointcut和 Advice。 要理解 Pointcut,必需知道 join point 是什么。join point 表示在程序执行中明确定义的点。AspectJ 中典型的 join point 包括方法调用、对类成员的访问以及异常处理程序块的执行。join point 可以包含其它 join point。例如,一个方法调用可能在它返回之前引起其它方法调用。那么,Pointcut 就是一种语言构造,这种构造根据已定义的标准挑选一组 join point。示例中的第一个 Pointcut 称为 既然 Aspect 已经定义了它应该记录的点,它使用 Advice 来完成实际的日志记录。Advice 是在 join point 之前、之后或周围执行的代码。相对于 Pointcut 来定义 Advice,说类似于“在想要记录的每个方法调用之后运行这些代码”这样的话。因此 Advice 如下:
Advice 使用
在示例中,传递到记录器的 清单 3. AutoLog Aspect 的输出
既然您已经对 Aspect 代码的形式特征有了更好的感性认识,让我们暂时把注意力转移到编写 Aspect 的工作上来吧。也就是说,让我们回答这个问题:“如何使上述代码起作用?” 要让 Aspect 影响正常的基于类的代码,必须将 Aspect 加入到它们要修改的代码中去。要使用 AspectJ 完成这个操作,必须使用 ajc 编译器来编译类和 Aspect 代码。ajc 既可以作为编译器也可以作为预编译器操作,生成有效的 .class 或 .java 文件,可以在任何标准 Java 环境(添加一个小的运行时 JAR)中编译和运行这些文件。 要使用 AspectJ 进行编译,将需要显式地指定希望在给定编译中包含的源文件(Aspect 和类)― ajc 不象 javac 那样简单地为相关导入模块搜索类路径。之所以这样做,是因为标准 Java 应用程序中的每个类都是相对分离的组件。为了正确操作,一个类只要求其直接引用的类的存在。Aspect 表示跨越多个类的行为的聚集。因此,需要将 AOP 程序作为一个单元来编译,而不能每次编译一个类。 通过指定包含在给定编译中的文件,您还可以在编译时插入或抽出系统的各种 Aspect。例如,通过在编译中包含或排除先前描述的日志记录 Aspect,应用程序构建器可以在 Cactus 框架中添加或除去方法跟踪。
AspectJ 当前版本的一个重要限制是其编译器只能将 Aspect 加入到它拥有源代码的代码中。也就是说,不能使用 ajc 将 Advice 添加到预编译类中。AspectJ 团队认为这个限制只是暂时的,AspectJ 网站承诺未来的版本(正式版 2.0)将允许字节码的修改。 AspectJ 发行版包含了几种开发工具。这预示着 AspectJ 将有美好的前景,因为它表明了那部分作者的一个重要承诺,使 AspectJ 对于开发人员将是友好的。对于面向 Aspect 的系统工具支持尤其重要,因为程序模块可能受到它们所未知的模块所影响。 随 AspectJ 一起发布的一个最重要的工具是图形结构浏览器,它展示了 Aspect 如何与其它系统组件交互。这个结构浏览器既可以作为流行的 IDE 的插件,也可以作为独立的工具。图 1 显示了先前讨论的日志记录示例的视图。 图 1.随 AspectJ 一起提供的图形结构浏览器展示了(在其它事物中)AutoLog 建议了哪些方法 除了结构浏览器和核心编译器之外,您还可以从 AspectJ 网站下载一个 Aspect 支持的调试器、一个 javadoc 工具、一个 Ant 任务以及一个 Emacs 插件。 让我们返回到语言特性。
Pointcut 和 Advice 允许影响程序的动态执行; 引入(Introduction)允许修改程序的静态结构。使用引入(Introduction),Aspect 可以向类中添加新的方法和变量、声明一个类实现一个接口或将检查异常转换为未检查异常(unchecked exception)。 假设您有一个表示持久存储的数据缓存的对象。要测量数据的“更新程度”,您可能决定向该对象添加时间戳记字段,以便容易地检测对象是否与后备存储器同步。然而,因为对象表示业务数据,就应该将这种机制性细节从对象中隔离。使用 AspectJ,可以用清单 4 中所显示的语法来向现有的类添加时间戳记: 清单 4. 向现有的类中添加变量和方法
除了必须限定在哪个类上声明引入的方法和成员变量以外(因此是
AspectJ 允许向接口和类添加成员,允许按 C++ 方式中混合样式继承。如果您希望使清单 4 中的 Aspect 通用化(generalize)以便能够对各种对象重用时间戳记代码,可以定义一个称为 清单 5. 将行为添加到接口
现在可以使用
既然已经定义了
使用 Pointcut,您可以方便地定义环境,加上时间戳记的对象将在其中更新戳记。清单 6 中显示了 清单 6. 在 Advice 中访问上下文
请注意,Pointcut 定义了 我发现定制编译错误是 AspectJ 最酷的特性之一。假如您希望隔离一个子系统,以使客户机代码必须通过中间媒介来访问工作程序对象(这种情况在 Facade 设计模式中出现)。使用 清单 7. 定义定制错误
除了 ajc 在编译时彻底检测 我承认 Java 语言中检查异常(checked exception)的价值。然而,我经常希望有一个变通方法,类似于“将该异常转换为运行时异常”这样简单的命令。有很多次,我正在编写的方法不对异常作出有意义的响应 ― 并且有时该方法的潜在使用程序也不响应。我不希望废弃异常,但是我也不希望为了找到它而一直跟踪到它的所有调用者。有一些巧妙的方法使用 try/catch 块来完成这个任务,但是没有一种象 AspectJ 中的 清单 8. 一个具有检查异常的类
如果我不使用 AspectJ 或在每个方法说明中声明异常,那么我将不得不插入 try/catch 块来处理已检出的 清单 9. soft 化异常
无论
AspectJ 是否值得使用?Grady Booch 将面向 Aspect 的编程看作是标志软件设计和编写方法基础性转变的三大运动之一。(请在 参考资料节中参阅他的“Through the Looking Glass”。)我赞同他的观点。AOP 解决了面向对象和其它过程性语言未能处理的一块问题空间。在最近几周我对 AspectJ 的介绍中,我发现它为那些我原以为是编程的基本极限的问题提供了一流的、可重用的解决方案。可以说 AOP 是从我开始使用对象以来的功能最强大的抽象。 当然,AspectJ 确实有一条学习曲线。和所有语言或语言扩展一样,在能够充分利用它的所有强大功能之前,您必需领会它的一些微妙之处。但是,学习曲线并不太陡峭 ― 在通读开发人员指南以及完成一些示例以后,我发现自己已经能够编写有用的 Aspect。AspectJ 给人的感觉是很自然,就好象它填补了您的编程知识缺陷而不是在一个新方向上的扩展。某些 AspectJ 工具还有些粗糙,但是我还没有遇到任何严重问题。 因为 AspectJ 具有对不可模块化的程序进行模块化的强大功能,我认为它值得立即使用。如果您的项目或公司还没有准备好在生产中使用 AspectJ,可以方便地将 AspectJ 应用于诸如调试和合同履行之类的开发事项。请注意一下这种语言扩展,这将对您有所帮助。
|