添加Maven依赖
如果采用 Maven 来管理项目,则可以在 pom.xml 文件中添加相关依赖。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.msdn</groupId> <artifactId>spring_aop</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.14</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.8.14</version> </dependency> </dependencies> </project> 复制代码
上述两种方法都可以达到同样的效果。
使用AspectJ编译器(ajc)
IDEA 默认使用javac
编译器,如果要使用 AspectJ 的编译器ajc
,需要在 IDEA 中进行相应的配置。
打开settings
对话框,然后做如下配置:
AspectJ简单示例
实际上,AspectJ 的用法非常简单,就像我们使用 JDK 编译、运行 Java 程序一样。下面通过一个简单的程序来示范 AspectJ 的用法,并分析 AspectJ 如何在编译时进行增强。
示例一
HelloWorld.java
public class HelloWorld { public void sayHello(){ System.out.println("Hello AspectJ"); } public static void main(String[] args) { HelloWorld hello = new HelloWorld(); hello.sayHello(); } } 复制代码
该类中有一个 sayHello()
方法,该方法打印出了一句话!
假设现在我们需要在 sayHello()
方法之前启动事务,当该方法结束时关闭事务,那么在传统的编程模式下,我们必须手动修改 sayHello()
方法。而如果使用 AspectJ,我们则不需要修改上面的方法,只需要添加一个切面即可。
TxAspect.aj
public aspect TxAspect { void around():call(void HelloWorld.sayHello()){ System.out.println("开始事务。。。"); proceed(); System.out.println("结束事务。。。"); } } 复制代码
上面的 TxAspect 根本不是一个 Java 类,所以 aspect 也不是 Java 支持的关键字,它只是 AspectJ 才能识别的关键字。 其后缀为.aj
,该文件的完整文件名为TxAspect.aj
。切面的语法只有AspectJ
可以识别,并使用其特殊的编译器ajc
来编译。
这段代码拦截Hello.sayHello()
方法,并在其执行之前开始事务,proceed()
方法代表回调原来的sayHello()
方法,执行结束后结束事务。
执行结果为:
开始事务。。。 Hello AspectJ 结束事务。。。 复制代码
从上面运行结果来看,我们完全可以不对 HelloWorld.java
类进行任何修改,就给它插入了事务管理的功能,这正是面向切面编程的意义所在。从这个例子中我们也可以体会到 AspectJ 的易学易用、无侵入(不需要继承任何类和接口)的特性。
示例二
除了上述事务管理的功能,还可以在 sayHello()
方法后增加记录日志的功能。我们再定义一个 LogAspect,
LogAspect.aj
public aspect LogAspect { // 定义一个 PointCut,其名为 logPointcut // 该 PointCut 对应于指定 HelloWorld 对象的 sayHello 方法 pointcut logPointCut():execution(void HelloWorld.sayHello()); // 在 logPointcut 之后执行下面代码块 after():logPointCut(){ System.out.println("记录日志。。。。"); } } 复制代码
上述代码定义了一个 Pointcut:logPointcut - 等同于执行 HelloWorld 对象的 sayHello()
方法,并指定在 logPointcut 之后执行简单的代码块,也就是说,在 sayHello()
方法之后执行指定代码块。
执行结果为:
开始事务。。。 Hello AspectJ 记录日志。。。。 结束事务。。。 复制代码
从上面运行结果来看,通过使用 AspectJ 提供的 AOP 支持,我们可以为 sayHello() 方法不断增加新功能。
为什么在对 HelloWorld 类没有任何修改的前提下,而 HelloWorld 类能不断地、动态增加新功能呢?这看上去并不符合 Java 基本语法规则啊。实际上我们可以使用 Java 的反编译工具来反编译前面程序生成的 HelloWorld.class 文件,发现 HelloWorld.class 文件的代码如下:
public class HelloWorld { public HelloWorld() { } public void sayHello() { try { System.out.println("Hello AspectJ"); } catch (Throwable var2) { LogAspect.aspectOf().ajc$after$com_msdn_aspectj_LogAspect$1$9e12ed77(); throw var2; } LogAspect.aspectOf().ajc$after$com_msdn_aspectj_LogAspect$1$9e12ed77(); } public static void main(String[] args) { HelloWorld hello = new HelloWorld(); sayHello_aroundBody1$advice(hello, TxAspect.aspectOf(), (AroundClosure)null); } } 复制代码
不难发现这个 HelloWorld.class 文件不是由原来的 HelloWorld.java 文件编译得到的,该 HelloWorld.class 里新增了很多内容,sayHello() 方法中增加了日志功能,主方法中增加了事务管理功能——这表明 AspectJ 在编译时“自动”编译得到了一个新类,这个新类增强了原有的 HelloWorld.java 类的功能,因此 AspectJ 通常被称为编译时增强的 AOP 框架。
问题记录
在进行案例测试的过程中,遇到了一系列的问题,总结归纳如下:
1、安装 AspectJ 时,我首先安装的是 aspectj-1.9.2 版本,但是后续实际测试过程中,由于在 IDEA 中配置AspectJ编译器时错误导致代码执行有误,当时我配置的情况如下图所示:
图中红框标记处,我以为是填写版本号,但是代码执行之后会报这样的错误:
错误信息为:
Error:ajc: Compliance level '1.8' is incompatible with target level '9'. A compliance level '9' or better is required 复制代码
原因:本地使用的 JDK 版本为 1.8,此处如果配置1.9的话,会导致 Javac 编译器配置也发生变化,导致错误发生。
所以这也是为啥我改为安装 aspectj-1.8.14,当时以为需要和 JDK 版本统一。但是实际上图中红框标识处根本不需要填写内容。如下图所示:
2、执行过程中遇到这样的错误,错误信息如下:
Error: java: Compliance level '1.6' is incompatible with target level '1.8'. A compliance level '1.8' or better is required 复制代码
点击 File 标签里的 Project Structure,选择 Project Settings->Modules,选择1.8版本对应的 language level。