目前,AspectJ 5 处在它的第二个里程碑版本,AspectJ 5 是 Java™ 平台上面向方面编程前进的一大步。AspectJ 5 主要的重点是对 Java 5 中引入的新 Java 语言特性(包括注释和泛型)提供支持。另外,AspectJ 5 还包含没有捆绑到 Java 5 的新特性,例如编写方面使用的基于注释的风格、改进的装入时织入以及新的方面实例化模型。现在请随这个项目的首席开发人员 Adrian Colyer 抢鲜了解 AspectJ 5,他将介绍 AspectJ 5 语言和包含 AspectJ 编译器及相关工具的版本。
AspectJ 5 (目前处在它的第二个里程碑版本)的主要重点是对 Java 5 中引入的新 Java 语言特性(包括注释和泛型)提供支持。AspectJ 5 还包含没有加入 Java 5 的新特性,例如基于注释的开发风格、改进的装入时织入和新的方面实例化模型。
在 AOP@Work 系列的这一期中,我概述了 AspectJ 5 语言和包含 AspectJ 编译器及相关工具的 AspectJ 5 版本。我先介绍如何用 AspectJ 5 编译器编译 Java 应用程序 (既可以用命令行编译器也可以用 AspectJ 开发工具(AJDT;请参阅 参考资料)),然后,我提供了使用 Java 5 特性实现 AspectJ 应用程序的一些示例。我还讨论了 擦除(erasure) 对 AOP 系统的意义,这是 Java 5 中用来实现泛型的技术,我还解释了 AspectJ 解决问题的方式。这篇文章中描述的有些特性只能用即将推出的 AspectJ 5 M3 版本编译(计划在 2005 年 7 月发布)。
也可以下载以下示例中使用的 AJDT 或命令行 AspectJ 编译器。请参阅 参考资料 获得技术下载的链接。
AspectJ 编译器 (ajc)支持在版本 1.3(及以前版本)、1.4 和 5.0 的兼容级别上编译 Java 源代码,并生成针对 1.1 版以上 VM 的字节码。像 javac 一样,ajc 有一些限制:在 1.4 版本兼容级别上编译源代码只支持 1.4 及以上版本的目标 VM,在 5.0 版本兼容级别上编译源代码只支持 5.0 版本的目标 VM。
![]() |
|
AspectJ 编译器的默认兼容级别 是使用 5.0 的源代码级别,并生成针对 5.0 VM 的字节码。可以传递 -1.5
编译器选项,显式地把源代码兼容级别和目标级别设置为针对 Java 5。假设想用 AspectJ 5 编译器处理 Java 1.4 语言并针对 1.4 VM,那么只需传递 -1.4
即可。
AspectJ 5 织入器也默认在 Java 5 兼容模式下运行。在这个模式中,织入器会正确地解释 Java 5 中的新特性;例如,编译器在确定 args(Integer)
是否匹配 int
参数时,会考虑自动装箱和拆箱。如果不是从源文件编译,而是用编译后的 Java 5 .class 文件 (在 inpath
上),使用 AspectJ 编译器来织入方面(在 aspectpath
上),那么这就是想要的行为。传递 -1.4
或 -1.3
选项会禁用 Java 5 特性。
AspectJ Development Environment Guide 包含更多关于新的编译器标志和选项的信息。请参阅 参考资料 一节访问这个指南。
如果正在用 AJDT 编译和运行 AspectJ 程序,那么 AspectJ 就继承了 Eclipse 中为 Java 编译指定的编译器选项。在这种情况下,可以对 AspectJ 进行配置,把 Java 5 模式作为工作区选项配置使用,或者以每个项目为基础使用。只要进入 Java 编译器选项配置页,把 Compiler compliance level
属性设置为 5.0
即可。如果正在从 JDK 1.4 升级,那么可能还需要在项目的 build 设置中把 JRE 系统库更新到 Java 5 JRE。
图 1 显示了 AJDT 的 Java 编译器选项配置页和一个用于 Java 5.0 兼容级别的选项配置设置。
图 1. 在 Eclipse 中指定 5.0 兼容级别

![]() ![]() |
![]()
|
有了这些基础知识,现在可以把注意力转到在 AspectJ 中使用 Java 5 特性了。我选择了一个示例方面,它支持一组基本的生命周期操作,可以用于任何被注释为 ManagedComponent
的类型 。ManagedComponent
是一个简单的标记注释,如下所示:
public @interface ManagedComponent {} |
方面本身被设计成可以表现许多 Java 5 和 AspectJ 5 语言的特性,包括枚举、注释、新风格的 for
循环以及泛型。LifecycleManager
方面的第一部分仅定义了 enum
,表示托管组件可能存在的状态,还定义了托管组件将会支持的 Lifecycle
接口,如清单 1 所示:
清单 1. 有 State 和 Lifecycle 声明的 LifecycleManager 方面
/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
public aspect LifecycleManager {
/**
* The defined states that a managed component can be in.
*/
public enum State {
INITIAL,
INITIALIZING,INITIALIZED,
STARTING,STARTED,
STOPPING,
TERMINATING,TERMINATED,
BROKEN;
}
/**
* The lifecycle interface supported by managed components.
*/
public interface Lifecycle {
void initialize();
void start();
void stop();
void terminate();
boolean isBroken();
State getState();
void addObserver(LifecycleObserver observer);
void removeObserver(LifecycleObserver observer);
}
...
|
方面的下一部分使用了一些新的 AspectJ 5 支持,以进行基于注释的类型匹配。这说明任何具有 ManagedComponent
注释的类型都要实现 Lifecycle
接口(并且因此稍后在方面中将会获得为此类组件定义的全部行为)。类型模式“@ManagedComponent *
”匹配具有 ManagedComponent
注释、名称任意的类型,如清单 2 所示:
清单 2. 用基于注释的类型匹配声明双亲
/** * Any type with an @ManagedComponent annotation implements * the Lifecycle interface (and acquires the default implementation * defined in this aspect if none is provided by the type). */ declare parents : @ManagedComponent * implements Lifecycle; |
清单 3 显示了 Lifecycle
中的添加/删除观察者操作中引用的 LifecycleObserver
接口的定义:
清单 3. LifecycleObserver 接口
/** * Interface to be implemented by any type needing to * observe the lifecycle events of managed components. */ public interface LifecycleObserver { void componentInitialized(Lifecycle component); void componentStarted(Lifecycle component); void componentStopped(Lifecycle component); void componentTerminated(Lifecycle component); void componentBroken(Lifecycle component); } |
对于没有提供自己的定义的所有实现者,方面提供了 Lifecycle
操作的默认实现。它还为所有实现者声明了私有的 state
和 observers
字段。注意 state
字段是枚举类型,而 observers
字段使用参数化类型,如清单 4 所示:
清单 4. Lifecycle 接口的默认实现
// default implementations for the state-based lifecycle events private State Lifecycle.state = State.INITIAL; public void Lifecycle.initialize() {} public void Lifecycle.start() {} public void Lifecycle.stop() {} public void Lifecycle.terminate() {} public boolean Lifecycle.isBroken() { return state == State.BROKEN; } public State Lifecycle.getState() { return state; } // default implementation of the add/remove observer lifecycle operations private List<LifecycleObserver> Lifecycle.observers = new ArrayList<LifecycleObserver>(); public void Lifecycle.addObserver(LifecycleObserver observer) { observers.add(observer); } public void Lifecycle.removeObserver(LifecycleObserver observer) { observers.remove(observer); } |
因为我想在这篇文章中介绍许多基础知识,所以从剩下的方面实现中我只摘录几个。对于每个生命周期事件,方面都提供 before
和 after returning
建议,以验证托管组件处于有效状态,从而执行操作并把变化通知给已注册的观察者,如下所示:
清单 5. 状态管理和通知
// these pointcuts capture the lifecycle events of managed components pointcut initializing(Lifecycle l) : execution(* Lifecycle.initialize(..)) && this(l); pointcut starting(Lifecycle l) : execution(* Lifecycle.starting(..)) && this(l); pointcut stopping(Lifecycle l) : execution(* Lifecycle.stopping(..)) && this(l); pointcut terminating(Lifecycle l): execution(* Lifecycle.terminating(..)) && this(l); /** * Ensure we are in the initial state before initializing. */ before(Lifecycle managedComponent) : initializing(managedComponent) { if (managedComponent.state != State.INITIAL) throw new IllegalStateException("Can only initialize from INITIAL state"); managedComponent.state = State.INITIALIZING; } /** * If we successfully initialized the component, update the state and * notify all observers. */ after(Lifecycle managedComponent) returning : initializing(managedComponent) { managedComponent.state = State.INITIALIZED; for (LifecycleObserver observer: managedComponent.observers) observer.componentInitialized(managedComponent); } |
注意建议体中使用了新风格的 for
循环,对所有已注册的观察者进行迭代。如果生命周期操作正常返回,就执行 after returning
建议。如果生命周期操作通过运行时异常而退出,那么后面的建议(清单 6)就把组件转变成 BROKEN
状态。可以想像,方面中会有进一步的建议,防止对状态是 BROKEN
的托管组件执行任何操作,但是这个讨论超出了这篇文章的范围:
清单 6. 故障检测和到 BROKEN 状态的转变
/** * If any operation on a managed component fails with a runtime exception * then move to the broken state and notify any observers. */ after(Lifecycle managedComponent) throwing(RuntimeException ex) : execution(* *(..)) && this(managedComponent) { managedComponent.state = State.BROKEN; for (LifecycleObserver observer: managedComponent.observers) observer.componentBroken(managedComponent); } |
示例方面已经表明,在方面中使用 Java 5 特性,就像在类中使用它们一样简单。而且从匹配的角度来看,根据注释的存在与否(在 declare parents
语句中),示例方面也给出了 AspectJ 5 能做什么的提示。但是在 AspectJ 5 中除了注释匹配之外还有许多东西,在下一节中会看到。
![]() ![]() |
![]()
|
在 AOP@Work 系列以前的文章中,介绍了注释、元数据和面向方面编程之间的关系 (请参阅 参考资料 一节中的 “AOP and metadata”),所以这里不再赘述,直接介绍 AspectJ 5 能做的一些事情。
出于示例的原因,我将采用 EJB 3.0 规范中的一些注释(请参阅 参考资料)。对于具有相关事务策略的方法,可以用 @Tx
注释进行注释。例如:
@Tx(TxType.REQUIRED) void credit(Money amount) {...} |
如果想编写 TransactionManager
方面,那么可能会对带有 @Tx
注释的方法的执行感兴趣。编写与它们匹配的切入点很简单,如清单 7 所示:
清单 7. 匹配事务性方法的执行
public aspect TransactionManager { /** * The execution of any method that has the @Tx * annotation */ pointcut transactionalMethodExecution() : execution(@Tx * *(..)); /** * Placeholder for implementing tx policies */ Object around() : transactionalMethodExecution() { return proceed(); } } |
execution(@Tx * *(..))
切入点表达式匹配任何方法的执行,可以使用任何名称、任何类型、任何参数,只要方法用 @Tx
注释。如果需要,也可以缩小范围。到事务性方法的匹配 调用 同样简单,只需编写“call(@Tx * *(..))
”即可。
在这种情况下,实现事务策略的建议需要知道执行方法上的 @Tx
注释的 value
。使用 AspectJ,可以把连接点的上下文值绑定到切入点表达式,然后向建议公布上下文。在 AspectJ 5 中,用新的切入点指示符 @annotation
把这个能力扩展到了注释上。像 AspectJ 中所有的其他上下文绑定切入点指示符一样,@annotation
扮演着双重角色:既把连接点匹配限制到主题(方法、字段、构造函数等)具有指定类型注释的连接点上,又公开那个值。可以很容易地重新定义 TransactionManager
方面,让它利用这一优点,如下所示:
清单 8. 公开注释值
public aspect TransactionManager { /** * The execution of any method that has the @Tx * annotation */ pointcut transactionalMethodExecution(Tx tx) : execution(* *(..)) && @annotation(tx); /** * Placeholder for implementing tx policies */ Object around(Tx tx) : transactionalMethodExecution(tx) { TxType transactionType = tx.value(); // do before processing Object ret = proceed(tx); // do after processing return ret; } } |
在使用 @annotation
来匹配注释时,注释类型必须拥有运行时持久性(否则 AspectJ 就不能在运行时公开注释值)。就像前面看到的,匹配只使用 execution
就能处理只具有类文件持久性的注释。
![]() |
|
到目前为止所展示的技术,处理的都是基于字段的连接点。假设有个字段用 @ClassifiedData
注释,那么可以编写清单 9 所示的两个切入点中的一个,具体取决于是否需要公开实际的注释值:
清单 9. 带注释的字段
/** * Any access or update to classified data */ pointcut classifiedAction() : get(@ClassifiedData * *) || set(@ClassifiedData * *); /** * Alternative declaration: * Any access or update to classified data, * exposes the annotation to provide access to * classification level attribute */ pointcut classifiedAction(ClassifiedData classification) : (get(* *) || set(* *)) && @annotation(classification); |
在结束关于注释的讨论之前,先深入研究一下 AspectJ 5 如何支持在类型上进行注释匹配的。AspectJ 允许指定类型模式,可以用注释模式 限定该模式。到目前为止,我一直用的都是最简单的注释模式 @Foo
,它在主题有 Foo
注释时匹配。可以进行组合,在主题既有 Foo
注释 又 有 Goo
注释时,“@Foo @Goo
”匹配。在主题或者有 Foo
注释或者 有 Goo
注释时,“@(Foo || Goo)
”匹配。请参阅 AspectJ 5 Developers Guide 中关于注释模式的讨论(在 参考资料 中),获取更多细节。
在 EJB 3.0 中,会话 bean 可以使用 @Stateful
或 @Stateless
进行注释。类型模式“@(Stateless || Stateful) *
”匹配的是有这两个注释之一的类型。出于某种原因,如果想把 TransactionManager
方面限制到只处理会话 bean,那么可以像下面这样重新定义 清单 8 的 transactionalMethodExecution
切入点。
pointcut transactionalMethodExecution(Tx tx) :
execution(* *(..)) && @annotation(tx)
&& within(@(Stateless || Stateful) *);
|
可以把这段代码读作“匹配在具有 Stateless
或 Stateful
注释的类型中带有 Tx
注释的任何方法的执行”。另一种编写它的方法是直接在切入点表达式中表达这个类型模式:execution(* (@(Stateless || Stateful) *).*(..))
,但是我认为前者更清楚。(注意,如果使用 call
则不是 execution
,那么两者之间会有显著差异:前者匹配从会话 bean 中发出的对事务方法的调用,而后者匹配对在会话 bean 中定义的事务方法的调用。)
![]() |
|
AspectJ 为匹配和公开注释定义了更多切入点指示符:
-
@withincode
- 匹配的连接点,由拥有指定注释的成员(方法、构造函数、建议)代码的执行产生。 @within
- 匹配的连接点在拥有指定注释的类型内部。 @this
-
在匹配的连接点中,对象目前绑定到有指定注释的
this
。
@target
- 匹配的连接点的目标有指定注释。 @args
- 匹配的连接点的参数拥有指定注释。
请参阅 AspectJ 5 Developers Guide 获取连接点匹配和注释的更多信息。
![]() ![]() |
![]()
|
Java 语言中对泛型的新支持是 Java 5 中引入的争议最大的变化。泛型 声明时使用一个或多个 类型参数,而这些类型参数在声明该类型的变量时绑定到具体的类型规范。泛型最常使用的示例是 Java 的集合类。Java 5 中的 List
接口是个泛型,带有一个类型参数 —— 列表中元素的类型。根据约定,单一字母用于表示类型参数,list 接口可能声明为:public interface List<E> {...}
。如果想创建引用字符串列表的变量,可以把它声明为类型 List<String>
。泛型 List<E>
把自己的类型参数 E
绑定到 String
,从而创建 参数化类型 List<String>
。
从 清单 4 显示的代码中摘出来的 LifecycleManager
方面,包含类型间声明中使用的参数化类型 (List<LifecycleObserver>
)的一个示例。AspectJ 5 也允许在泛型上进行类型间声明。清单 10 显示了一个泛型 DataBucket<T>
,以及一个在它上面进行类型间声明的方面:
清单 10. 泛型上的类型间声明
public class DataBucket<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } aspect DataHistory { private E DataBucket<E>.previousDataValue; private E DataBucket<E>.getPreviousDateValue() { return previousDataValue; } } |
注意,类型间声明中使用的类型参数名称不必与 DataBucket
类本身声明中使用的类型参数名称对应;相反,类型的 签名 必须匹配(类型参数的数量,以及通过 extends
或 super
子句放在类型参数上的限制)。
这一节的其余部分,我把重点放在切入点表达式中通用签名和类型的匹配上。在随后的讨论中,把切入点指示符分成两种类型是有帮助的:一类是基于静态签名进行匹配的切入点指示符(execution
、call
、get
、 set
,等等),另一类是根据运行时类型信息进行匹配的切入点指示符(this
、target
、args
)。由于存在叫做 擦除(erasure) 的东西(我马上就会介绍),所以这个区分很重要。
对于基于签名进行匹配的切入点指示符,AspectJ 采取了一种简单方式:类型的类型参数规范就是签名的一部分。 例如,以下方法都是不同的签名:
void process(List<Number> numbers)
void process(List<? extends Number> numbers)
void process(List<?> items)
这些方法的执行可以用以下切入点分别匹配:
execution(* process(List<Number>))
execution(* process(List<? extends Number>))
execution(* process(List<?>))
AspectJ 在匹配类型的时候,支持 *
和 +
通配符。表达式“execution(* process(List<*>))
”匹配全部三个 process
方法,因为 *
匹配任何类型。但是,表达式“execution(* process(List<Number+>))
“只匹配第一个 process
方法(Number
由模式 Number+
匹配),但是 不匹配 第二个或第三个。可以把模式 List<Number+>
扩展到与 List<Float>、List<Double>、List<Integer>
等匹配,但是对于 List<? extends Number>
来说,这些都是不同的签名。有一个重要的区别是,请考虑这样一个事实:在 process
方法的方法体内,用没有通配的签名插入列表是合法的,但是在使用 ? extends
格式的时候就不合法了。
需要记住的规则是:泛型通配符是签名的组成部分,而且 AspectJ 模式通配符被用来 匹配 签名。
在根据运行时类型信息进行匹配时,事情变得更有趣了。this
、target
和 args
切入点指示符全都根据运行时类型信息进行匹配。请考虑 process
方法的另一个变体:
void process(Number n) {...} |
可以静态地决定切入点表达式“execution(* process(..)) &&args(Number)
”以总是 匹配这个方法的执行 —— 传递的参数保证是数字。相反,如果编写的是“execution(* process(..)) &&args(Double)
”,那么这个表达式可能 匹配这个方法的执行,具体取决于实际运行时传递的参数的类型。在这种情况下,AspectJ 应用运行时测试来判断参数是不是 instanceof Double
。
现在再考虑一下采用参数化类型的 process
方法的以下签名:
void process(List<? extends Number> ns) {...} |
然后应用相同的推断,就可以看出:
- 总是会匹配,因为不论传递什么类型的列表,都必须满足这个规范。
-
永远不会匹配,因为字符串列表永远不会传递给这样的方法,该方法期待得到扩展
Number
的东西的列表。 - 可能 匹配,具体取决于实际传递的列表是数字列表、双精度列表,还是浮点列表。
execution(* process(..)) &&args(List<? extends Number>)
execution(* process(..)) && args(List<String>)
execution(* process(..)) && args(List<Number>)
在后一种情况下,可能 做的工作就是在实际的参数上应用运行时测试,判断它是不是 instanceof List<Number>
。不幸的是,Java 5 实现泛型时采用了一种叫做 擦除(erasure) 的技术 —— 被擦除的就是参数化类型的运行时类型参数信息。在运行时,参数只被当作普通的 List
(所谓参数的“原始”类型)。
![]() |
|
即使缺少必要的信息进行确定的决策,AspectJ 也必须决定这类切入点是否应当匹配。在 Java 语言中对于这类情况有一种优先级:在把原始类型(例如 List
)的实例传递给需要参数化类型(例如 List<Number>
)的方法时,调用会通过 Java 5 编译器传递,但是会生成一个“unchecked”警告,提示转换可能不是类型安全的。
类似地,当 AspectJ 认为某个切入点可能匹配指定连接点,但是不能应用运行时测试进行确定的时候,就会考虑对切入点进行匹配,而且 AspectJ 编译器会提出一个“unchecked”警告,表示实际的匹配不能被检测。就像 Java 语言支持 @SuppressWarnings
注释,可以在成员中抑制未检测警告一样,AspectJ 支持 @SuppressAjWarnings
注释,可以用它对建议进行注释,以抑制从切入点匹配发生的未检测警告。
在离开泛型主题之前,有一个要点需要考虑。回到 清单 10 中定义的 DataBucket
类,请注意不论用多少不同的类型参数去实例化 DataBucket
的实例(如下所示),都只有一个 DataBucket
类:
DataBucket<Integer> myIntBucket = new DataBucket<Integer>(); DataBucket<String> myStringBucket = new DataBucket<String>(); DataBucket<Food> myFoodBucket = new DataBucket<Food>(); ... |
它的含义就是,在 DataBucket
类的内部,没有返回 String
、Integer
或 Food
实例的 getData
方法执行这样的东西。相反,只有一个 getData
方法执行,返回类型参数 T
的实例。所以可以这样编写,它匹配的方法执行,是在命名类型 DataBucket
中返回 T
的 getData
方法,其中 T
是类型参数:
execution<T>(T DataBucket<T>.getData()) |
关于在 AspectJ 5 中对泛型的完整处理,请参阅 AspectJ 5 Developers Guide。
![]() ![]() |
![]()
|
介绍了 AspectJ 5 的新发行版中最重要的 Java 5 语言特性之后,我现在把重点转到一些没有显式地捆绑到 Java 5 的新特性上。其中最重要的一个是方面声明的新的基于注释的风格,称作 @AspectJ 注释。在 AspectJ 5 中,可以用普通的 Java 语法编写方面,然后对声明进行注释,这样,它们就可以由 AspectJ 的织入器解释。例如,在代码风格中,可以这样编写:
public aspect TransactionManager {
...
|
在基于注释的风格中,可以这样编写:
@Aspect
public class TransactionManager {
...
|
随着 AspectJ 与 AspectWerkz 在 2005 年初的合并,@AspectJ 注释被添加到 AspectJ 中。它们使得任何标准的 Java 5 编译器都可以处理 AspectJ 源代码,而实际上任何从 Java 源代码起工作的工具都可以。在使用没有为操作 AspectJ 程序提供集成支持的 IDE 时(当然,这类环境也缺乏显示横切结构的视图),它们也为日常的编辑体验造成显著区别。
需要注意的重点是,AspectJ 5 发行版具有以下内容(虽然有两种开发风格):
- 一个 语言
- 一个 语义
- 一个 织入器
不论选择用什么风格表达方面,它们实际表达的都是同样的东西,而且也用同样的方式发挥作用。这一重要属性使得可以容易地混合和匹配风格(所以用 @AspectJ
风格开发的方面可以与用代码风格开发的方面结合使用,反之亦然)。但是 @AspectJ
风格有些限制。例如,在使用常规的 Java 编译器时,就不支持注释风格版本的 AspectJ 构造(例如 declare soft
),因为这类构造需要编译时支持而不是织入时支持。
现在来看一个使用 @AspectJ 注释的示例。
我先从清单 11 显示的简化的 LifecycleManager
方面开始,并用 @AspectJ
风格重写它:
清单 11. 简化的生命周期管理器方面,代码风格
/** * This aspect provides default lifecycle management for all * types with the @ManagedComponent annotation. */ public aspect LifecycleManager { /** * The defined states that a managed component can be in. */ public enum State { INITIAL, INITIALIZING,INITIALIZED, STARTING,STARTED, STOPPING, TERMINATING,TERMINATED, BROKEN; } /** * The lifecycle interface supported by managed components. */ public interface Lifecycle { void initialize(); void start(); void stop(); void terminate(); boolean isBroken(); State getState(); } /** * Any type with an @ManagedComponent annotation implements * the Lifecycle interface (and acquires the default implementation * defined in this aspect if none is provided by the type). */ declare parents : @ManagedComponent * implements Lifecycle; // default implementations for the state-based lifecycle events private State Lifecycle.state = State.INITIAL; public void Lifecycle.initialize() {} public void Lifecycle.start() {} public void Lifecycle.stop() {} public void Lifecycle.terminate() {} public boolean Lifecycle.isBroken() { return state == State.BROKEN; } public State Lifecycle.getState() { return state; } // these pointcuts capture the lifecycle events of managed components pointcut initializing(Lifecycle l) : execution(* Lifecycle.initialize(..)) && this(l); pointcut starting(Lifecycle l) : execution(* Lifecycle.starting(..)) && this(l); pointcut stopping(Lifecycle l) : execution(* Lifecycle.stopping(..)) && this(l); pointcut terminating(Lifecycle l): execution(* Lifecycle.terminating(..)) && this(l); /** * Ensure we are in the initial state before initializing. */ before(Lifecycle managedComponent) : initializing(managedComponent) { if (managedComponent.state != State.INITIAL) throw new IllegalStateException("Can only initialize from INITIAL state"); managedComponent.state = State.INITIALIZING; } /** * If we successfully initialized the component, update the state and * notify all observers. */ after(Lifecycle managedComponent) returning : initializing(managedComponent) { managedComponent.state = State.INITIALIZED; } ... } |
方面声明和内部的类型声明可以容易地转移到新风格,如清单 12 所示:
清单 12. 简化的生命周期管理器方面,注释风格
/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
@Aspect
public class LifecycleManager {
/**
* The defined states that a managed component can be in.
*/
public enum State {
INITIAL,
INITIALIZING,INITIALIZED,
STARTING,STARTED,
STOPPING,
TERMINATING,TERMINATED,
BROKEN;
}
/**
* The lifecycle interface supported by managed components.
*/
public interface Lifecycle {
void initialize();
void start();
void stop();
void terminate();
boolean isBroken();
State getState();
}
...
|
下面,我们来看看用 @AspectJ 风格重写切入点和建议时发生了什么。切入点是在与切入点具有相同签名的 void
方法上使用 @Pointcut
注释而编写的。建议则是在方法上使用 @Before、 @Around、@AfterReturning、@AfterThrowing
和 @After
注释而编写的,如清单 13 所示:
清单 13. 注释风格的切入点和建议
/** * This aspect provides default lifecycle management for all * types with the @ManagedComponent annotation. */ @Aspect public class LifecycleManager { ... // these pointcuts capture the lifecycle events of managed components @Pointcut( "execution(* org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.initialize(..)) && this(l)" ) void initializing(Lifecycle l) {} @Pointcut( "execution(* org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.starting(..)) && this(l)" ) void starting(Lifecycle l){} @Pointcut( "execution(* org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.stopping(..)) && this(l)" ) void stopping(Lifecycle l) {} @Pointcut( "execution(* org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.terminating(..)) && this(l)" ) void terminating(Lifecycle l) {} /** * Ensure we are in the initial state before initializing. */ @Before("initializing(managedComponent)") public void moveToInitializingState(Lifecycle managedComponent) { if (managedComponent.state != State.INITIAL) throw new IllegalStateException("Can only initialize from INITIAL state"); managedComponent.state = State.INITIALIZING; } /** * If we successfully initialized the component, update the state and * notify all observers. */ @AfterReturning("initializing(managedComponent)") public void moveToInitializedStated(Lifecycle managedComponent) { managedComponent.state = State.INITIALIZED; } |
注意,在切入点表达式中,任何引用的类型都必须是全限定的(导入语句只能在源代码条件下存在,在处理注释时,对织入器不可用)。建议方法必须声明成 public
,并返回 void
(@Around
建议除外,它必须返回值)。
现在剩下的就是把类型间声明也转移到 @AspectJ 风格的方面了。在注释风格中,这些声明像清单 14 所示这样进行:
清单 14. 注释风格的类型间声明
/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
@Aspect
public class LifecycleManager {
...
// default implementations for the state-based lifecycle events
@DeclareParents("@ManagedComponent *")
class DefaultLifecycleImpl implements Lifecycle {
private State state = State.INITIAL;
public void initialize() {}
public void start() {}
public void stop() {}
public void terminate() {}
public boolean isBroken() { return state == State.BROKEN; }
public State getState() { return state; }
}
...
}
|
关于注释风格的开发,还有许多其他有趣的问题,例如如何在注释风格的建议方法的方法体内引用 thisJoinPoint
,proceed
在 around 建议中是如何被支持的。要获取这些主题的更多信息,请参阅 AspectJ 5 Developers Guide。
![]() ![]() |
![]()
|
装入时织入 指的是在类装入 VM 时织入类的过程(比照提前织入而言 —— 例如编译时织入)。从 1.1 发行版起,AspectJ 就拥有支持装入时织入必需的基础设施,但是必须编写定制的类装入器,才能真正把 AspectJ 的织入器集成进应用程序。在 AspectJ 1.2 发行版中,随着添加了 aj
脚本,装入时织入得到改进,aj 能够从命令行装入和运行任何 Java 应用程序,也可以在类装入时从 ASPECTPATH
织入方面。这个脚本支持 JDK 1.4 以上版本。
但是,命令行脚本不能方便地用在所有环境,特别是不能很好地与 Java EE 应用程序集成。在 AspectJ 5 中,通过放在类路径中的 META-INF/aop.xm,AspectJ 支持对装入时织入进行配置。这是随着 2005 年初与 AspectWerkz 的合并而带给 AspectJ 的另一个特性。
现在来看看 aop.xml 文件和它的相关元素。
aop.xml 文件包含两个主要小节:aspects
元素定义用于装入时织入的方面集合,weaver
元素指定控制织入器行为的选项(主要是控制应当织入哪个类型)。清单 15 显示了一个示例文件:
清单 15. 示例 aop.xml 文件
<aspectj> <aspects> <!-- declare two existing aspects to the weaver --> <aspect name="com.MyAspect"/> <aspect name="com.MyAspect.Inner"/> <!-- define a concrete aspect inline --> <concrete-aspect name="com.xyz.tracing.MyTracing" extends="tracing.AbstractTracing"> <pointcut name="tracingScope" expression="within(com.xyz..*)"/> </concrete-aspect> </aspects> <weaver options="-XlazyTjp"> <include within="com.xyz..*"/> </weaver> </aspectj> |
在 aspects
元素中,或者通过名称,或者在 aop.xml 文件内部定义,把已知的方面定义到织入器。后一种技术只能用于扩展现有抽象方面(有一个或多个抽象切入点):切入点表达式在 XML 中提供。对于“基础设施”方面,这可以是把配置(切入点表达式)外部化的很好方法。定义了织入器中的方面集合之后,如果需要(上面代码中没显示),可以使用一个或多个可选的 include
和 exclude
元素,控制在织入过程中实际使用哪些方面。默认情况下,织入器使用所有定义的方面。
weaver
元素包含传递给织入器的选项,和应当被织入(通过 include
语句)的类型集合的一个可选定义。如果没有指定 include
语句,那么所有类型都可供织入器进行织入。
如果在类路径中有多个 META-INF/aop.xml 文件,那么它们的内容就聚合在一起,形成传递给织入器的完整规范。
AspectJ 5 支持许多 agents
,可以把装入时能力集成到现有环境。清单 16 显示了使用 JVMTI (Java 5) 代理时的示例 JVM 启动选项,任何符合 Java 5 规范的 JVM 都支持它:
清单 16. JVMTI 代理
-javaagent=aspectjweaver.jar |
AspectJ 5 还自带了 JRockit 代理,它支持的功能与 Java 5 之前的 JRockit VM 一样(JRockit 还支持 Java 5 上的 jvmti
)。等价的启动选项是 -Xmanagement:class=org.aspectj.weaver.tools. JRockitWeavingAgent
。
在 AspectJ 5 Developers Guide 中,可以发现关于利用 AspectJ 5 进行装入时织入的更多细节。
![]() ![]() |
![]()
|
总之,AspectJ 5 代表 AspectJ 前进的一大步。这篇文章的重点主要是新的发行版既支持对 Java 5 语言构建的完全编译,还支持基于注释和泛型的连接点匹配。
AspectJ 5 发行版中最令人兴奋的两个特性 —— 新的基于注释的开发风格和对 AspectJ 装入时织入支持的增强 —— 是 AspectJ 与 AspectWerkz 合并的结果。因为合并的重要性和特性的相关性,我在这里对它们都进行了深入讨论。
当然,一篇文章不可能覆盖 AspectJ 5 这样全面的发行版的全部增强。例如,我重点介绍了 AspectJ 中对连接点匹配的主要更新,而把相对次要的(例如处理自动装箱和协变返回类型的新方式)留给您自己去发现。其他没有在这里介绍,但是值得研究的特性包括:新的 pertypewithin
方面实例化模型、在运行时询问方面类型的反射 API、对 declare soft
处理运行时异常的方式的修订、兼容性、织入性能的提高,等等。
读到这里,我可以肯定,您可以猜测得到从哪继续学习这些(及更多)新特性。您猜对了,就是 AspectJ 5 Developers Guide。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- 相关文章:
- AspectJ 5 Developer's Guide 提供了对 AspectJ 5 中全部新特性的完整描述。
- 要学习更多关于使用 AspectJ Developer Kit 的内容,请参阅“Develop aspect-oriented Java applications with Eclipse and AJDT”(developerWorks,2004 年 9 月)。
- AOP@Work 是为期一年的系列文章,专门帮助您把 AOP 整合进日常的 Java 编程中。不要错过本系列中的每篇文章。请参阅 完整的系列列表。特别与这篇文章有关的是 Ramnivas Laddad 的对 AOP 和元数据的两部分的研究。第 1 部分 提供了对 Java 5 中注释的概念性概述,以及 AOP 如何从添加元数据注释获益;第 2 部分 提供了一系列关于有效地把元数据和 AOP 组合的指南,并讨论了元数据注释对于采用面向方面编程的影响。
- Brett McLaughlin 的两部分的“Tiger 中的注释,第 1 部分: 向 Java 代码中添加元数据”(developerWorks,2004 年 9 月)是一份使用 Java 5 内置注释特性的优秀介绍。
- 请参阅 Brian Goetz 的“Java 理论和实践: 了解泛型”(developerWorks,2005 年 1 月),获得在学习使用 Java 5 泛型工具时,识别和避免一些缺陷的指南。
- AspectJ site on Eclipse.org 包含许多 AspectJ 相关文章、文档和下载的链接。
- AJDT 提供了一套完整的工具,用于在 Eclipse 中开发 AspectJ 应用程序。
- AspectJ Development Environment Guide 提供了使用命令行编译器和相关工具的信息。
- 作者的 blog 包含的系列文章大体上介绍了 AspectJ 5 的新特性以及用 AspectJ 进行面向方面编程。
- EJB 3.0 规范 包含用于 EJB 的注释的更多细节。
- 在 developerWorks Java 技术专区 可以找到 Java 编程各方面的文章。
- 还请参阅 Java 技术专区教程页,获得 developerWorks 免费的以 Java 为重点的教程。