欢迎您光临本小站。希望您在这里可以找到自己想要的信息。。。

使用面向 Aspect 的编程改进模块性

架构&设计模式 water 2087℃ 0评论

人们认识到,传统的程序经常表现出一些不能自然地适合单个程序模块或者几个紧密相关的程序模块的行为,因此面向 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
3
4
5
6
7
public void doGet(JspImplicitObjects theObjects) throws ServletException
{
 logger.entry("doGet(...)");
 JspTestController controller = new JspTestController();
 controller.handleRequest(theObjects);
 logger.exit("doGet");
}

作为该项目代码约定的一部分,要求每个开发人员将这几行代码插入到他或她所编写的任何方法中。还要求开发人员记录每个方法的参数。如果没有对部分项目监督进行详尽的代码复查,就很难履行这种约定。在 1.2 版本中,大约有 80 个单独的记录日志调用分布在 15 个类中。在框架的 1.3 版本中,用单一 Aspect 代替了这 80 多个调用,该 Aspect 自动记录参数和返回值以及方法入口和出口。清单 2 包含了这个 Aspect 的一个作了很大简化的版本(例如,我省略了参数和返回值日志记录)。

清单 2. 自动应用于每个方法的记录日志调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public aspect AutoLog{
  
 pointcut publicMethods() : execution(public * org.apache.cactus..*(..));
 pointcut logObjectCalls() :
   execution(* Logger.*(..));
    
 pointcut loggableCalls() : publicMethods() && ! logObjectCalls();
    
 before() : loggableCalls(){
   Logger.entry(thisJoinPoint.getSignature().toString());