![c5ff8cd6354a2691309fa5a4a45afd49.png](https://i-blog.csdnimg.cn/blog_migrate/bf59987b1a7d4a9d6249e83e5ebbd4ff.jpeg)
【干货点】 此处是【好好面试】系列文的第11篇文章。看完该篇文章,你就可以了解Spring中Aop的相关使用和原理,并且能够轻松解答Aop相关的面试问题。更重要的是,很多人其实一看源码就头大,这次专门将个人阅读源码的整个调试过程一步步呈现出来,希望对你们有一定的帮助。
上篇文章比较轻松诙谐的描述了Aop的由来和实际应用【传送门:http://www.imooc.com/article/289010 】,答应过大家要补充一篇相关原理分析的文章,该篇文章会从SpringAop做了什么、相关原理一步步铺开讲。
大前提
看完上篇文章都知道,我这边定义了一个切面
![15737ef7e8a250218cfe6c49d9ac443e.png](https://i-blog.csdnimg.cn/blog_migrate/0bfaec1f9889d26c5bbd3eaa98a67f41.jpeg)
该切面定义了PointCut、Advice ,以及JoinPoint,之后定义了业务类BuyService和业务类ChatService,接下来我会通过源码跟踪的模式讲解下SpringAop做了什么。
Spring Aop做了什么【开始源码跟踪阅读】
首先给出Main类
![b6e7e14e8b2a90fd7f315e0b77704121.png](https://i-blog.csdnimg.cn/blog_migrate/b2cf3f18aa2c3ea80b53f050439b7aa0.jpeg)
可以看到我这里用的是AnnotationConfigApplicationContext,解释下
AnnotationConfigApplicationContext是一个用来管理注解bean的容器,所以我可以用该容器取得我定义了@Service注解的类的实例。
打断点后,启动程序,我们可以看到TestDemo的实例在idea的表现是这样的
![280b9dd90750170323345ec20040f447.png](https://i-blog.csdnimg.cn/blog_migrate/3b77364a3c34160bb019f3e21d49bbfe.jpeg)
而BuyService的实例却不同
![a26408f8b0643442e25360a0d6435769.png](https://i-blog.csdnimg.cn/blog_migrate/eb04a789e41ab99667738c80bb30fcef.jpeg)
我们可以从看到BuyService是SpringCGLIB强化过的一个实例,那么问题来了
- 为什么BuyService被强化过而TestDemo没有?
- SpringCGLIB又是什么?
- Spring是在什么时候生成一个强化后的实例的?
带着这些疑问,让我们一步步从Spring源码中找到答案。
为什么BuyService被强化过而TestDemo没有?
这个问题比较简单,我们可以看回上面我对切片的定义
![719a031f439cf0fa5665ea491b4eed32.png](https://i-blog.csdnimg.cn/blog_migrate/35b1ff874d6db0621279d4c6d64468f8.png)
可以从代码中看出,我定义的切点是*Service命名的类,而TestDemo很明显不符合这个设定,因此TestDemo逃过被强化的命运。
SpringCGLIB又是什么?
CGLIB其实就是一种实现动态代理的技术,利用了ASM开源包,先将代理对象类的class文件加载进来,之后通过修改其字节码并且生成子类。结合demo来解读便是SpringCGLIB会先将BuyService加载到内存中,之后通过修改字节码生成BuyService的子类,该子类便是强化后的BuyService,上文看到的强化后的实例便是该子类的实例。
Spring是在什么时候生成一个强化后的实例的?
这个便厉害了,首先,我们要先从Spring如何加载切片入手。
【思考Time】 为什么我会选择从切片入手呢?原因很简单,Spring就是因为发现了切片,并且对切片进行解析后才知道了要强化哪些类。
![3be16cd44b2117aa4e05344d7dea29e7.png](https://i-blog.csdnimg.cn/blog_migrate/53ce57fd1349def2be18323ee835f0da.jpeg)
切片的处理第一步便是要加上@Aspect注解,学过注解的都知道,注解的作用更多的是标志识别,也就是告诉Spring这个类要做相关特殊处理,因此我们可以基于该认识,反调该注解使用的地方
![c2b7d57b55298e27de58894e948e6bda.png](https://i-blog.csdnimg.cn/blog_migrate/c99f63fbd7b924ed78e6cebea4801f57.jpeg)
可以从截图看出,我反调了@Aspect后定位到了AbstractAspectJAdvisorFactory类中的hasAspectAnnotation函数,并且携带参数clazz,因此我猜测该接口就是用来识别clazz是否使用了注解@Aspect的地方,于是我打上了断点,并且加了条件 clazz == AuthAspect.class ,重新启动后
![64ef1466291710ba0f490fc3de9a78ab.png](https://i-blog.csdnimg.cn/blog_migrate/1100c0cc31c4024d025bd7a01db150ab.jpeg)
我们看到确实被断点到了,可以得出我的猜测是对的。
我们先看下断点后做了什么事情,之后再看下具体是哪里进行了扫描。在断点处按F8继续往下走,最后发现
![f80a1ee7dfd7b0f5967d839dd3be55c3.png](https://i-blog.csdnimg.cn/blog_migrate/11e557edcc7efb9530b15b31d7140136.jpeg)
没错,可以看到最终是构建成了一个Advisor对象 ,并且放入了BeanFactoryAspectJAdvisorsBuilder中的advisorsCache中,这样意味着Spring最终会将使用了@Aspect注解的类构建成Advisor对象后保存进BeanFactoryAspectJAdvisorsBuilder.advisorsCache中。
接下来我们看看具体是哪里进行了使用@Aspect注解的相关类的扫描,这次我断点的地方在BeanFactoryAspectJAdvisorsBuilder中的advisorsCache调用了put的地方。
【思考Time】 为什么我会选择在advisorsCache调用了put的地方打断点呢?原因很简单,因为我们上面已经分析出@Aspect注解的类构建成Advisor对象后保存进BeanFactoryAspectJAdvisorsBuilder.advisorsCache中,而我通过反调知道put的地方只有一个,因此我可以断定在此处打断点可以知道到底哪里进行了扫描的操作。
![b5eb1f3d9e129881faf2211eaaca91e2.png](https://i-blog.csdnimg.cn/blog_migrate/2c24ea5622a6e8eb5f79afa5134a3aec.jpeg)
通过打断点后我从idea的Frames面板中看到
![8cd6376dfd0a6ec1da92da53a3517da8.png](https://i-blog.csdnimg.cn/blog_migrate/02b86c58aefc833e4541968abaa67435.jpeg)
没错,做了扫描@Aspect注解的扫描器是AbstractAutoProxyCreator类
![650b319a79d40a5911b3701ea9f16d3a.png](https://i-blog.csdnimg.cn/blog_migrate/1c39219d6d36b576d775676a78ac4b26.png)
![7ea402238585b5dc5954015395ee45f7.png](https://i-blog.csdnimg.cn/blog_migrate/1e1199a4267ad1a45cc7adf6f508ece7.png)
我们可以从中看到AbstractAutoProxyCreator最终实现了InstantiationAwareBeanPostProcessor接口。
【思考Time】 这个接口有什么作用呢?具体可以看我前阵子写的一篇文章:https://mp.weixin.qq.com/s/r2OEqsap6NgaEnNveO1mVg
现在已经找到了扫描注解的地方,并且我们也看到了最终是生成了Advisor对象 ,并且放入了BeanFactoryAspectJAdvisorsBuilder中的advisorsCache中,那么Spring是在什么时候生成强化后的实例的呢?
接下来我的切入点是AbstractAutoProxyCreator中的postProcessAfterInitialization接口。
【思考Time】 之所以会选择AbstractAutoProxyCreator为切入点,是因为通过命名可以看出这是SpringAop用来构建代理[强化]对象的地方,并且由于SpringCGLIB是先将目标类加载到内存中,之后通过修改字节码生成目标类的子类,因此我猜测强化是在目标类实例化后触发postProcessAfterInitialization的时候进行的。
因此我在postProcessAfterInitialization接口中做了断点,并且加了调试条件。
![049142253279b36c936a0574d82a1596.png](https://i-blog.csdnimg.cn/blog_migrate/ab5495964451a860d7e081565fa3b2ec.jpeg)
可以看到我这里断点到了ChatService这个类。
【思考Time】 为什么专门断点ChatService这个类?之所以会专门定位这个类,因为我的切面的目标类就包含了ChatService,通过定位到该类,我们可以一步步捕捉Spring的强化操作。
我们可以看到,生成强化后的对象就藏在wrapIfNecessary中。
【思考Time】 为什么我会知道是生成强化后的对象就藏在wrapIfNecessary中呢?因为我通过调试发现,在调用了wrapIfNecessary接口后,返回的对象是强化后的对象。
那么问题来了,为什么Spring会知道ChatService类需要进行进行强化呢?我们可以从wrapIfNecessary中走入更深一层,通过调试,可以看到
![1b3a5b6701ed73a945e9a52a181ba9e7.png](https://i-blog.csdnimg.cn/blog_migrate/97900d1a1bc9b1a8437799e3a5669ad1.jpeg)
在此处会从advisorsCache中根据aspectName取出对应的Advisor。拿到Advisor后,便是进行过滤的地方了,通过F8往后走,可以看到过滤的地方在AopUtils.canApply接口中。
![644b913cb07d7db0d29f50bcb57f432d.png](https://i-blog.csdnimg.cn/blog_migrate/731369018c3d6d2f375381a90b223e66.jpeg)
可以看到此处传进来的targetClass符合切面的要求,因此可以进行构建强化对象。
接下来让我们看下真正产生强化对象的地方了
![57e899d8b5d2ba6ec593351b502fcb4c.png](https://i-blog.csdnimg.cn/blog_migrate/a017c24df42e7b42992c52734680361e.jpeg)
我们可以看到在AbstractAutoProxyCreator的createProxy函数中看到,最后会构造出一个强化后的chatService。
那么createProxy又做了什么呢?通过断点一层层深入后,发现最后会到达
![5f92dd2338ba6f9ca7fad4eddb55058a.png](https://i-blog.csdnimg.cn/blog_migrate/be38fce16c68937614c00ad68f0ef862.jpeg)
通过源码分析,我们发现在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实现动态代理。
作者:饭谈编程
链接:http://www.imooc.com/article/289462
来源:慕课网
本文首次发布于慕课网 ,转载请注明出处,谢谢合作