前言
Spring AOP 主要具有三种使用方式,分别是注解、XML 配置、API,目前在 Spring 中,由于 XML 需要大量配置,注解已经逐步取代 XML 配置,而 API 需要对 Spring 底层具有较深入的了解才能使用,因此注解成了应用 Spring 的首选方式。在 Spring AOP 中,Spring 又使用了 AspectJ 的注解,既然 Spring 单独提出一个 AOP 模块,那它为什么自己不提供一套注解?Spring AOP 和 AspectJ 又有何不同呢?本篇将尝试对这两者的区别与联系进行解释说明。
AspectJ 入门
既然 Spring AOP 使用到了 AspectJ 的注解,那么就有必要对 AspectJ 做一个初步的认识。
首先 AspectJ 是对 AOP 实现的框架,如果你对 AOP 的基础概念不太熟悉,可以参阅《从代理到 AOP,如何实现一个 AOP 框架?》。AspectJ 提供了独有的语法,通过自己的编译器对语法进行分析,然后将相关逻辑织入到目标类的字节码中。
1. 依赖引入
在 maven 项目中,可以引入 aspectj-maven-plugin 插件,这个插件会在编译时使用 AspectJ 的编译器。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<dependencies>
<!-- 升级 AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
<configuration>
<!-- 打印编织信息 -->
<showWeaveInfo>true</showWeaveInfo>
<!-- 配置 JDK 版本号 -->
<source>1.8</source>
<target>1.8</target>
<complianceLevel>1.8</complianceLevel>
</configuration>
<executions>
<execution>
<goals>
<!-- 使用这个目标来编织所有的主类 -->
<goal>compile</goal>
<!-- 使用这个目标来编织所有的测试类 -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
AspectJ 编译器会在生成的字节码中加上 AspectJ 自身的注解及相关类,因此还需要引入 aspectjrt 依赖。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
2. AspectJ 示例
同样以日志打印为例,我们希望打印出目标方法的执行参数及返回内容。
public interface IService {
String doSomething(String param);
}
public class ServiceImpl implements IService {
@Override
public String doSomething(String param) {
return "param is : " + param;
}
}
AspectJ 同时支持 .aj
文件和在 Java 类中使用注解,注解的使用方式与在 Spring AOP 中相同,我们创建一个 ServiceAspect.aj 文件,内容如下。
public aspect ServiceAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
pointcut executionPointcut():execution(String IService.doSomething(String));
before():executionPointcut(){
logger.info("before:param is:{}", thisJoinPoint.getArgs());
}
after() returning(String result):executionPointcut(){
logger.info("after returning:result is:{}", result);
}
}
这里定义了一个 Aspect,和 Java 类相似,这里的关键字是 aspect,AspectJ 编译器会将这个文件编译为一个同名的类。
同时声明了一个名为 executionPointcut 的 Pointcut,以便 advice 复用,executionPointcut 拦截 IService.doSomething 方法的执行。
我们还在 Aspect 中定义了两个 Advice,这两个 Advice 指定了上述定义的 Pointcut,它们会在目标方法执行前后执行,然后分别打印目标方法的参数和返回值。
编写一个测试类。
public class App {
public static void main(String[] args) {
IService service = new ServiceImpl();
service.doSomething("hello,aspectj");
}
}
代码执行如下。
17:43:58.493 [main] INFO com.zzuhkp.blog.aspectj.ServiceAspect - before:param is:hello,aspectj
17:43:58.506 [main] INFO com.zzuhkp.blog.aspectj.ServiceAspect - after returning:result is:param is : hello,aspectj
成功拦截到目标方法的执行。
Spring AOP 设计目标
除了方法执行时拦截,AspectJ 还可以对字段、构造器等 Joint Point 进行拦截,作为一个专业的 AOP 框架来说功能更为强大。Spring 为什么又提出一个 AOP 框架呢?下面看下 Spring AOP 的设计目标。
Spring AOP 与大多数其他 AOP 框架有所不同,它并没有提供最完整的 AOP 实现。Spring AOP 的目标是提供 AOP 和 IOC 之间的紧密继承,以帮助解决应用程序中的常见问题。Spring 中的 Aspect 是使用普通的 bean 语法配置的,这是它和其他 AOP 实现的关键区别。Spring AOP 能解决大多数问题,对于 Spring AOP 不能解决的问题,使用 AspectJ 是最佳的选择。
Spring AOP 认为 AspectJ 是一个优秀的框架,它不与 AspectJ 竞争以提供全面的 AOP 解决方案,而是和 AspectJ 互补。Spring 将 Spring AOP 和 IOC 与 AspectJ 无缝集成,如果用户不喜欢使用 AspectJ 的注解,还可以通过 XML 配置来使用 Spring AOP。
Spring AOP 与 AspectJ 有何异同
根据前面的部分大致对这两者的不同做一个总结。
- AspectJ 是一个完整的 AOP 实现,依赖特定的编译器和语法,在编译期织入字节码来实现 AOP。
- Spring AOP 非完整 AOP 实现,Spring AOP 目的是为了和 IOC 容器整合,仅可选的提供了对 AspectJ 注解的支持,通过运行时创建目标类的代理来实现 AOP。