共计 3046 个字符,预计需要花费 8 分钟才能阅读完成。
在上一篇 AspectJ 的入门中,简单的介绍了下 AspectJ 的使用,主要是以 AspectJ 的 example 作为例子。介绍完后也留下了几个问题:1)我们在 spring 中并没有看到需要 aspectj 之类的关键词,而是使用 java 代码就可以了,这是如何做到的 2)Spring 中如何做到不使用特殊的编译器实现 aop 的(AspectJ 如何在运行期使用)3)Spring 源码中与 aspectJ 相关的 AjType 究竟是啥?
这篇文章会继续试着解决这几个问题。
aspectJ 的几种织入方式
compile-time、post-compile 和 load-time Weavers
首先了解下 AspectJ 的几种织入方式,分别是 compile-time、post-compile 和 load-time,分别对应着编译期、后编译期、加载期织入
编译期织入
首先是编译期织入,上一篇博客所介绍的方式就是使用的编译期织入。很容易理解,普通的 java 源码 + aspectJ 特殊语法的‘配置’文件 + aspectJ 特殊的编译器,编译时候生成已织入后的.class 文件,运行时直接运行即可。
后编译期织入
后编译期织入和编译期的不同在于,织入的是 class 字节码或者 jar 文件。这种形式,可以织入一个已经织入过一次的切面。同样这种情况也需要特殊的编译器
加载期织入
加载期顾名思义,是在类被加载进虚拟机之前织入,使用这种方式,须使用 AspectJ agent。
了解了这些概念,下面就要知道,spring 是使用哪种呢?spring 哪一种都不是,spring 是在运行期进行的织入。
Spring 如何使用 AspectJ
AspectJ 本身是不支持运行期织入的,日常使用时候,我们经常回听说,spring 使用了 aspectJ 实现了 aop,听起来好像 spring 的 aop 完全是依赖于 aspectJ
其实 spring 对于 aop 的实现是通过动态代理(jdk 的动态代理或者 cglib 的动态代理),它只是使用了 aspectJ 的 Annotation,并没有使用它的编译期和织入器,关于这个可以看这篇文章,也就是说 spring 并不是直接使用 aspectJ 实现 aop 的
spring aop 与 aspectJ 的区别
看了很多篇博客以及源码,我对 spring aop 与 aspectJ 的理解大概是这样;1)spring aop 使用 AspectJ 语法的一个子集,一些 method call, class member set/get 等 aspectJ 支持的语法它都不支持 2)spring aop 底层是动态代理,所以受限于这点,有些增强就做不到,比如 调用自己的方法就无法走代理
看下下面的例子:
@Component
public class A{
public void method1(){
method2();
}
public void method2(){
//…
}
}
这个时候 method2 是无法被切到的,要想被切到可以通过如下奇葩的方式:
@Component
public class A{
@Autowired
private A a;
public void method1(){
a.method2();
}
public void method2(){
//…
}
}
之前碰到这样的问题时,我还特别不能理解,现在想下 aop 的底层实现方式就很容易理解了。
在之前写的 jdk 动态代理与 cglib 动态代理实现原理,我们知道了 jdk 动态代理是通过动态生成一个类的方式实现的代理,也就是说代理是不会修改底层类字节码的,所以可能生成的代理方法是这样的
public void method1(){
// 执行一段代码
a.method1()
// 执行一段代码
}
public void method2(){
// 执行一段代码
a.method2()
// 执行一段代码
}
回头看 a.method1() 的源码,也就明白了,为啥 method2() 没有被切到,因为 a.method1() 执行的方法,最后调用的不是 代理对象.method2(),而是它自己的 method2()(this.method2()) 这个方法本身没有任何改动
反观 aspectJ,aspectJ 是在编译期修改了方法(类本身的字节码被改了),所以可以很轻松地实现调用自己的方法时候的增强。
3)spring aop 的代理必须依赖于 bean 被 spring 管理,所以如果项目没有使用 spring,又想使用 aop,那就只能使用 aspectJ 了(不过现在没有用 spring 的项目应该挺少的吧。。。)
4)aspectJ 由于是编译期进行的织入,性能会比 spring 好一点 5)spring 可以通过 @EnableLoadTimeWeaving 开启加载期织入(只是知道这个东西,没怎么研究。。有兴趣的可以自己去研究下)6)spring aop 很多概念和 aspectJ 是一致的
AspectJ 的注解在 spring aop 中的应用
了解了 spring 与 aspectJ 的关系后,就能更清晰的了解 spring 的 aop 了。
先说明一点,虽然我介绍 aspect 的配置时,一直介绍的 aspectJ 文件配置方式,但是 aspectJ 本身是支持注解方式配置的。可以看官方文档,注解在 aspectJ 中的使用
而 spring 使用了 aspectJ 注解的一小部分(正如前面所说的,受限于 jdk 的动态代理,spring 只支持方法级别的切面)
回头看看 AjType
回头看看之前看到的这段源码,什么是 AjType,经过 aspectJ 解析器解析后对类的一种描述,比如正常的方法可能是这样
/*
* 配置前置通知, 使用在方法 aspect() 上注册的切入点
* 同时接受 JoinPoint 切入点对象, 可以没有该参数
*/
@Before(“aspect()”)
public void before(JoinPoint joinPoint) {
log.info(“before ” + joinPoint);
}
在 AjType 中就能获取到很多其他的 aspectJ 所需的相关信息(除了 java 反射所能获取到的信息以外)
/**
* Return the pointcut object representing the specified pointcut declared by this type
*/
public Pointcut getDeclaredPointcut(String name) throws NoSuchPointcutException;
/**
* Return the pointcut object representing the specified public pointcut
*/
public Pointcut getPointcut(String name) throws NoSuchPointcutException;
比如看着两个方法,可以获取到切入点信息。
在看看 PerClauseKind.SINGLETON 这里就复用了 aspectJ 的概念,详细可以看这篇文章最后部分
总结下
这篇文章回答了之前学习 aspectJ 时候碰到的几个问题,然后讨论了下 aspectJ 在 spring 中的应用。最大的收获是了解了 spring 与 aspectJ 的关系,了解了两者对 aop 的不同实现所造成的使用上的影响。以后当遇到了 spring aop 相关的概念如果不理解,可以去 aspectJ 上去搜搜看了。
参考文章:Intro to AspectJspring 使用 load-time weavingspring aop 和 aspectJ 的比较
本文作者:端吉阅读原文
本文为云栖社区原创内容,未经允许不得转载。