乐趣区

好好面试手把手调试教你分析SpringAop

【干货点】 此处是【好好面试】系列文的第 11 篇文章。看完该篇文章,你就可以了解 Spring 中 Aop 的相关使用和原理,并且能够轻松解答 Aop 相关的面试问题。更重要的是,很多人其实一看源码就头大,这次专门将个人阅读源码的整个调试过程一步步呈现出来,希望对你们有一定的帮助。

上篇文章比较轻松诙谐的描述了 Aop 的由来和实际应用【传送门:https://mp.weixin.qq.com/s/tQ…】,答应过大家要补充一篇相关原理分析的文章,该篇文章会从 SpringAop 做了什么、相关原理一步步铺开讲。

大前提

看完上篇文章都知道,我这边定义了一个切面

该切面定义了 PointCut、Advice,以及 JoinPoint,之后定义了业务类 BuyService 和业务类 ChatService,接下来我会通过源码跟踪的模式讲解下 SpringAop 做了什么。

Spring Aop 做了什么【开始源码跟踪阅读】

首先给出 Main 类

可以看到我这里用的是 AnnotationConfigApplicationContext,解释下

AnnotationConfigApplicationContext 是一个用来管理注解 bean 的容器,所以我可以用该容器取得我定义了 @Service 注解的类的实例。

打断点后,启动程序,我们可以看到 TestDemo 的实例在 idea 的表现是这样的

而 BuyService 的实例却不同

我们可以从看到 BuyService 是 SpringCGLIB 强化过的一个实例,那么问题来了

  • 为什么 BuyService 被强化过而 TestDemo 没有?
  • SpringCGLIB 又是什么?
  • Spring 是在什么时候生成一个强化后的实例的?

带着这些疑问,让我们一步步从 Spring 源码中找到答案。

为什么 BuyService 被强化过而 TestDemo 没有?

这个问题比较简单,我们可以看回上面我对切片的定义

可以从代码中看出,我定义的切点是 *Service 命名的类,而 TestDemo 很明显不符合这个设定,因此 TestDemo 逃过被强化的命运。

SpringCGLIB 又是什么?

CGLIB 其实就是一种实现动态代理的技术,利用了 ASM 开源包,先将代理对象类的 class 文件加载进来,之后通过修改其字节码并且生成子类。结合 demo 来解读便是 SpringCGLIB 会先将 BuyService 加载到内存中,之后通过修改字节码生成 BuyService 的子类,该子类便是强化后的 BuyService,上文看到的强化后的实例便是该子类的实例。

Spring 是在什么时候生成一个强化后的实例的?

这个便厉害了,首先,我们要先从 Spring 如何加载切片入手。

【思考 Time】 为什么我会选择从切片入手呢?原因很简单,Spring 就是因为发现了切片,并且对切片进行解析后才知道了要强化哪些类。

切片的处理第一步便是要加上 @Aspect 注解,学过注解的都知道,注解的作用更多的是标志识别,也就是告诉 Spring 这个类要做相关特殊处理,因此我们可以基于该认识,反调该注解使用的地方

可以从截图看出,我反调了 @Aspect 后定位到了 AbstractAspectJAdvisorFactory 类中的 hasAspectAnnotation 函数,并且携带参数 clazz,因此我猜测该接口就是用来识别 clazz 是否使用了注解 @Aspect 的地方,于是我打上了断点,并且加了条件 clazz == AuthAspect.class,重新启动后

我们看到确实被断点到了,可以得出我的猜测是对的。
我们先看下断点后做了什么事情,之后再看下具体是哪里进行了扫描。在断点处按 F8 继续往下走,最后发现

没错,可以看到最终是构建成了一个 Advisor 对象,并且放入了 BeanFactoryAspectJAdvisorsBuilder 中的 advisorsCache 中,这样意味着 Spring 最终会将使用了 @Aspect 注解的类构建成 Advisor 对象后保存进 BeanFactoryAspectJAdvisorsBuilder.advisorsCache 中。

接下来我们看看具体是哪里进行了使用 @Aspect 注解的相关类的扫描,这次我断点的地方在 BeanFactoryAspectJAdvisorsBuilder 中的 advisorsCache 调用了 put 的地方。

【思考 Time】 为什么我会选择在 advisorsCache 调用了 put 的地方打断点呢?原因很简单,因为我们上面已经分析出 @Aspect 注解的类构建成 Advisor 对象后保存进 BeanFactoryAspectJAdvisorsBuilder.advisorsCache 中,而我通过反调知道 put 的地方只有一个,因此我可以断定在此处打断点可以知道到底哪里进行了扫描的操作。

通过打断点后我从 idea 的 Frames 面板中看到

没错,做了扫描 @Aspect 注解的扫描器是 AbstractAutoProxyCreator 类


我们可以从中看到 AbstractAutoProxyCreator 最终实现了 InstantiationAwareBeanPostProcessor 接口。

【思考 Time】 这个接口有什么作用呢?具体可以看我前阵子写的一篇文章:https://mp.weixin.qq.com/s/r2…

现在已经找到了扫描注解的地方,并且我们也看到了最终是生成了 Advisor 对象,并且放入了 BeanFactoryAspectJAdvisorsBuilder 中的 advisorsCache 中,那么 Spring 是在什么时候生成强化后的实例的呢?
接下来我的切入点是 AbstractAutoProxyCreator 中的 postProcessAfterInitialization 接口。

【思考 Time】 之所以会选择 AbstractAutoProxyCreator 为切入点,是因为通过命名可以看出这是 SpringAop 用来构建代理 [强化] 对象的地方,并且由于 SpringCGLIB 是先将目标类加载到内存中,之后通过修改字节码生成目标类的子类,因此我猜测强化是在目标类实例化后触发 postProcessAfterInitialization 的时候进行的。

因此我在 postProcessAfterInitialization 接口中做了断点,并且加了调试条件。

可以看到我这里断点到了 ChatService 这个类。

【思考 Time】 为什么专门断点 ChatService 这个类?之所以会专门定位这个类,因为我的切面的目标类就包含了 ChatService,通过定位到该类,我们可以一步步捕捉 Spring 的强化操作。

我们可以看到,生成强化后的对象就藏在 wrapIfNecessary 中。

【思考 Time】 为什么我会知道是生成强化后的对象就藏在 wrapIfNecessary 中呢?因为我通过调试发现,在调用了 wrapIfNecessary 接口后,返回的对象是强化后的对象。

那么问题来了,为什么 Spring 会知道 ChatService 类需要进行进行强化呢?我们可以从 wrapIfNecessary 中走入更深一层,通过调试,可以看到

在此处会从 advisorsCache 中根据 aspectName 取出对应的 Advisor。拿到 Advisor 后,便是进行过滤的地方了,通过 F8 往后走,可以看到过滤的地方在 AopUtils.canApply 接口中。

可以看到此处传进来的 targetClass 符合切面的要求,因此可以进行构建强化对象。
接下来让我们看下真正产生强化对象的地方了

我们可以看到在 AbstractAutoProxyCreator 的 createProxy 函数中看到,最后会构造出一个强化后的 chatService。
那么 createProxy 又做了什么呢?通过断点一层层深入后,发现最后会到达

通过源码分析,我们发现在 AbstractAutoProxyCreator 构建强化对象的时候是调用了 createAopProxy 函数,重点来了,我们可以看到针对 targetClass,也就是 ChatService 做了判断,如果 targetClass 有实现接口或者 targetClass 是 Proxy 的子类,那么使用的是 JDK 的动态代理实现 AOP,如果不是才会使用 CGLIB 实现动态代理。

那么 JDK 实现的动态代理和 CGLIB 实现的动态代理有什么区别吗?
首先动态代理可以分为两种:JDK 动态代理和 CGLIB 动态代理。从文中我们也可以看出,当目标类有接口的时候才会使用 JDK 动态代理,其实是因为 JDK 动态代理无法代理一个没有接口的类。JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,而 CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,并且覆盖其中的方法。

Aop 实现机制之代理模式

本来想一篇文章说完源码跟踪分析 Aop 和 Aop 的实现机制代理模式,发现源码跟踪分析已经很占篇幅了,因此没办法只能再开一篇文章专门阐述 Aop 的实现机制代理模式,期待下篇文章。

文章总结

从上面的源码阅读并且分析可以看出

  • 强化后的 ChatService 实例是在 ChatService 实例化后产生的,也就是 AbstractAutoProxyCreator.postProcessAfterInitialization 后。
  • 之所以 Spring 能够识别的出来为什么 ChatService 实例需要进行强化,是因为在这一步之前 Spring 先使用 AbstractAutoProxyCreator 扫描了使用注解 @Aspect 的类,并且构造成了 Advisor 对象后放入了 advisorsCache 中。
  • 从 advisorsCache 取出来后对 ChatService 类进行识别,使用的是 AopUtils.canApply。识别通过后,便会走入 AbstractAutoProxyCreator.createProxy 函数中,从中构建真正的强化对象。
  • 在构建强化对象的时候,走的是 DefaultAopProxyFactory.createAopProxy,并且会对目标类进行判断,如果 targetClass 有实现接口或者 targetClass 是 Proxy 的子类,那么使用的是 JDK 的动态代理实现 AOP,如果不是才会使用 CGLIB 实现动态代理。

公众号主营:服务端编程相关技术解说,具体可以看历史文章。

公众号副业:各种陪聊吹水,包括技术、就业、人生经历、大学生活、内推等等。

欢迎关注,一起侃大山

退出移动版