前言
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。