在编程语言发展史中,可重用库的引入很大地提高了编写程序的准确性和快速性,提高了软件重用能力,面向过程的编程和面向对象的编程都对可重用库提供了支持,那么面向方面的编程也不例外。本文介绍了方面库的概念,以及如何使用AspectJ构造和使用方面库。
1.方面库 大家都知道,各种AOP工具的核心就是切入点(pointcut)和通知(advice)的声明。切入点描述了主程序执行与方面执行相遇的地方,也就是被横切的位置;通知则描述了在程序执行过程中遇到匹配的切入点时应当采取什么行动。假设已经开发了一个方面,并且感觉它适用于其他项目,那么可以泛化这个方面,并把它隔离到自己独立的项目中,形成一个库,即方面库(Aspect Library)。方面库提供了某个功能的内部执行逻辑和基础设施,通过切入点的实例化将方面库与某个特定项目连接起来。例如提供应用程序性能监视的方面库,实现了所有性能监视相关的方法和通知,某应用程序使用该库的时候,只需要把库的切入点定义为应用特定的连接点即可,而无需关心性能监视功能的具体实现。这就是方面库的基本概念。
方面库是AOP工具具有扩展性的体现,目前常用的AOP工具,如JBoss AOP、Spring AOP、AspectJ等,都有对方面库的支持,但由于它们实现AOP的方法不同,方面库的定义和使用方法也不相同。在JBoss AOP和Spring AOP中,通知的实现都是通过普通Java语法定义,切入点到通知的绑定是通过显式的XML文档或者注释实现的。所以将方面库应用于应用程序就可以很容易地通过在 XML 中或通过注释定义新的通知绑定而实现。并且JBoss和Spring系统本身已经提供了很好的方面库供用户使用。
AspectJ是Java 语言语法和语义的扩展,它提供了自己的一套处理方面的关键字,这些都决定了用AspectJ构造方面库的方法具有一定的特殊性。所以AspectJ构造的方面库一直不象JBoss AOP和Spring AOP那样普遍,AspectJ也并没有提供任何方面库。令人欣慰的是, AspectJ从AspectJ5版本开始提供对注释的支持,它的编辑工具AJDT中逐渐加入了对方面库的支持功能,从而使得使用AspectJ构造方面库变得越来越容易。本文就来介绍如何用AspectJ构造方面库。
2.AspectJ对方面库的技术支持 方面库的实现在于切入点的实例化方式。AspectJ对切入点定义方法的支持导致了两种完全不同的方面库实现方法--使用抽象方面(abstract aspect)的方法和使用注释的方法。本章将详细介绍这两种方式。
AspectJ是Java语言的扩展。方面在AspectJ语言中用aspect关键字标示,它类似于Java类,可以定义成抽象的,也存在继承关系。切入点在AspectJ语言中用关键字pointcut标示,它有一套完整的语法来描述切入点,也可以定义成抽象的,即没有实际定义的切入点。抽象pointcut只能定义在抽象方面中。如清单 1所示,抽象方面A里面定义了一个抽象pointcut名叫publicCall。抽象方面如同抽象类一样,可以被继承,继承抽象方面的方面必须重载抽象pointcut,即赋予抽象pointcut实际的定义。如清单 2所示,方面B继承了抽象方面A,重载了名叫publicCall的pointcut,给它一个具体的定义。
清单 1 抽象方面
public abstract aspect A {
abstract pointcut publicCall(int i);
}
清单 2 继承抽象方面的子方面
public aspect B extends A {
pointcut publicCall(int i): call(public Foo.m(int)) && args(i);
}
AspectJ对继承和抽象的支持正是我们构造方面库的基础。抽象方面包含抽象的切入点和具体的通知,正符合方面库的特征,可以使用抽象方面来构造方面库文件。继承抽象方面的子方面必须具体化切入点,可以把它当作方面库在具体应用程序中的实施。为了加深理解,我们将在接下来的章节中使用抽象方面技术制作一个简单的记录踪迹的方面库,并扩展它以应用到具体项目中。
从AspectJ 5开始支持的注释是另外一种构造方面库的技术基础。Java5引入了注释这种类型,它以注释的形式来表达程序中各成员的元数据信息,采用符号@标示。Java5中可以被注释修饰的Java程序成员有很多,AspectJ 5能支持的注释包括修饰方面、方法、属性、构造函数和通知,修饰方法和通知的参数的注释也能支持,但是不支持pointcut和declare语句上的注释。为了支持注释类型,AspectJ 5扩展了pointcut语法,可以匹配存在或者不存在的注释类型。例如清单 3中的名叫onewayMethod的pointcut可以匹配所有被注释@Oneway修饰的方法调用。
清单 3 含有注释的pointcut
public aspect C {
pointcut onewayMethod: call(@Oneway * *(..));
}
AspectJ 5对注释的支持简化了实施库的方法,我们可以很容易地想到,在把方面库实施到应用程序时,可以利用注释标明具体的切入点的位置。在构造方面库文件时,只需要定义好与注释相关的切入点,并规定该切入点上的具体的通知内容就可以了。我们同样会在下面的章节中介绍使用注释制作简单的方面库的过程。
3.使用抽象方面构造方面库 曾经有一段时间,我们只有在获得了所有源代码的情况才才能顺利的编译用到了AspectJ的软件项目。现在情况当然已经和以往大大不同,由于AspectJ今年来的迅速发展,在项目中使用AspectJ的要求大大放松了。我们可以在没有源代码的情况下进行AOP开发。无论是程序主逻辑部分还是Aspect部分,都可以用jar文件的形式提供,并且能够顺利的通过AspectJ的编译。
下面我们将通过一个例子演示如何在AJDT中构造Aspect库。在构建这个例子的过程中,我们用到了Eclipse及其上的AJDT插件。这两个工具可以分别从http://www.eclipse.org 和http://www.eclipse.org/ajdt 上下载。AJDT从多个方面扩展了Eclipse的Java开发环境使其支持面向方面的编程。当在Eclipse平台上安装了AJDT插件之后,我们现在能新建AspectJ项目和创建新的Aspect了(图 1)。
图 1 AJDT提供的Wizard
利用图 1中所示的向导,我们建立一个AspectJ Project来存放我们的方面库。我们将这个工程命名为sample.aspects.library。随后,我们可以利用AJDT提供的工具按钮建立一个新的Aspect, 用到的工具栏按钮如图 2所示。
图 2 使用工具栏按钮建立一个新的Aspect
通过AJDT提供的向导,我们建立了如下的Aspect:
清单 4 抽象方面AbstractTrace
package sample.aspects.library;
public abstract aspect AbstractTrace {
public abstract pointcut TraceScope();
Object around():TraceScope(){
Object sig = thisJoinPoint.getSignature();
System.out.println("Enter "+sig);
Object res = proceed();
System.out.println("Exit "+sig);
return res;
}
}
清单 4中建立了一个名为AbstractTrace的抽象方面,它包含了一个pointcut和一个advice。我们看到,名为TraceScope的pointcut也被标明是抽象的。根据AspectJ的规范,一个抽象的方面是不会起作用的。抽象方面存在的唯一目的是为了被用户继承。接下来,我们将整个工程导出成为一个.jar文件。在AspectJ中,我们可以将这种形式的Jar交给用户,它们随后就可以利用继承来使用方面库提供的功能了。
如果我们希望在一个新的工程中使用方面库,我们需要在工程中指定用到的方面库,并继承方面库中提供的抽象方面。在Eclipse中,我们首先将这个jar文件加入到工程的"Java Build Path"中,就如同使用一个普通的Java类库一样。随后,我们就可以利用AJDT提供的向导来继承抽象方面了,如图 3所示。
图 3 继承抽象Aspect
在图 3中,我们可以指定在新的Aspect中实现所有的抽象pointcut,这样能让AJDT帮助我们多做一些工作。最后生成并修改后的代码如清单 5所示,它具体化了抽象pointcut,指明这个方面库应用的范围,即切入点是任何函数执行的时刻。
清单 5 继承抽象Aspect
package aspects;
import sample.aspects.library.AbstractTrace;
public aspect Trace extends AbstractTrace {
public pointcut TraceScope():execution(* *(..));
}
从清单 5中我们可以看出,通过使用已有的方面库,用户需要编写的代码变得非常简洁。商务逻辑的开发人员可以不去了解AspectJ的一些高级用法,他们只需要指定哪些时候需要用到方面中提供的功能就可以了。
4.使用注释构造方面库 前面介绍了使用抽象方面构造一个记录踪迹的方面库并实施到具体应用中,这章我们采用注释技术构造一个方面库,以完成同样的功能。
与前面一章相同,首先创建一个AspectJ的工程,工程名为sample.annotation.library。需要注意的是,因为要在工程中使用注释类型,所以JDK依从的版本必须为5.0。如果你目前的缺省设置不是5.0,请如图所示在JDK Compliance中选择"为工程指定一个",设置为5.0。或者在工程的properties对话框中选择Java Compiler页面修改。
图 4 修改JDK Compilance
接下来要做的就是新建一个标准Java注释类型,在Eclipse中点击File->New->Other弹出New对话框,选择Java下面的Annotation,设定它的Package为sample.annotation.library,名称叫做trace,其他接受缺省值,点击完成。这样,一个名叫trace的注释类型就被创建出来了,我们将基于它创建一个记录踪迹的方面库。
接下来创建方面库中的Aspect,即一个aj文件,文件如清单 6所示。
清单 6 捕捉注释的Aspect
package sample.annotation.library;
public aspect AnnotationTrace {
pointcut traceScope():execution(@trace * *(..));
Object around():traceScope(){
Object sig = thisJoinPoint.getSignature();
System.out.println("Enter "+sig);
Object res = proceed();
System.out.println("Exit "+sig);
return res;
}
}
这样,方面库的编码工作就完成了,与上一个例子类似,我们把它打包成tracelib.jar文件备用。
现在我们来介绍如何应用方面库tracelib.jar。创建一个新的AspectJ项目sample.annotation.application,注意,因为要使用注释类型,修改JDK Compliance为5.0。新建一个标准Java类business.logic.MainLogic,添加简单函数testTrace(),用这个Java类来模拟应用程序。
为了使用方面库,首先必须将方面库的jar文件添加到AspectJ Aspect Path中,在工程的properties对话框中选择AspectJ Aspect Path页面添加方面库的jar文件,如图 5所示。在应用程序MainLogic类中使用记录踪迹的功能时,只需要简单的将@trace添加到想要使用这个功能的函数前面即可。MainLogic类的源码如清单 7所示。将MainLogic类作为AspectJ/Java Application来运行,可以看到输出结果,在testTrace()调用之前和之后,加入了记录踪迹的语句,如清单 8所示。这样就达到了我们使用方面库tracelib.jar的目的。相比较而言,通过注释技术实现的方面库使用起来是更为便捷。
图 5 添加AspectJ Aspect Path
清单 7 应用程序MainLogic源码
package business.logic;
import sample.annotation.library.trace;
public class MainLogic {
@trace
public static void testTrace(){
System.out.println("call testTrace function.");
}
public static void main(String[] args) {
MainLogic.testTrace();
}
}
清单 8 MainLogic输出结果
Enter void business.logic.MainLogic.testTrace()
call testTrace function.
Exit void business.logic.MainLogic.testTrace()
5.总结 与OOP中的可重用库类似,AOP方面库可以在整个工程或者多个工程中重复使用,从而提高程序编写的准确性和方便性。使用方面库的编程人员只需要了解简单的AOP知识就能享受到AOP带来的好处,把方面库实施到具体的应用中。本文在介绍方面库基本概念的基础上,介绍了利用AspectJ构造方面库的两种技术,希望能够对AOP在应用项目中广泛应用有所帮助。