京东一面:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?我懵了。。
共 4676字,需浏览 10分钟
· 2022-02-26
点击关注公众号,Java干货及时送达
作者:半分、
来源:https://www.cnblogs.com/semi-sub/p/13548479.html
前言
在使用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring已经帮我们去解决循环依赖问题,对我们开发者来说是无感知的,下面具体分析一下spring是如何解决bean之间循环依赖,为什么要使用到三级缓存,而不是二级缓存?
bean生命周期
首先大家需要了解一下bean在spring中的生命周期,bean在spring的加载流程,才能够更加清晰知道spring是如何解决循环依赖的。
另外,Spring 系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。
![](https://filescdn.proginn.com/327abc34462561428b3514ca1fc6a502/a60295d8ff90d4ea9ff97a74581fd5a0.webp)
我们在spring的BeanFactory工厂列举了很多接口,代表着bean的生命周期,我们主要记住的是我圈红线圈出来的接口, 再结合spring的源码来看这些接口主要是在哪里调用的
![](https://filescdn.proginn.com/82f97982a625e1a94338d0262220e0d1/691977072ec0c90cb3b21779645b9eee.webp)
AbstractAutowireCapableBeanFactory类的doCreateBean方法是创建bean的开始,我们可以看到首先需要实例化这个bean,也就是在堆中开辟一块内存空间给这个对象,createBeanInstance方法里面逻辑大概就是采用反射生成实例对象,进行到这里表示对象还并未进行属性的填充,也就是@Autowired注解的属性还未得到注入
![](https://filescdn.proginn.com/ca57440836361de47030b97d25f7073c/d1d267c3e73808a3b32ef11aa0d1acc7.webp)
我们可以看到第二步就是填充bean的成员属性,populateBean方法里面的逻辑大致就是对使用到了注入属性的注解就会进行注入,如果在注入的过程发现注入的对象还没生成,则会跑去生产要注入的对象,第三步就是调用initializeBean方法初始化bean,也就是调用我们上述所提到的接口
![](https://filescdn.proginn.com/f18f65131b328c577ae4ef4727c68c74/26c99931031ebd63e507353a07a277b4.webp)
可以看到initializeBean方法中,首先调用的是使用的Aware接口的方法,我们具体看一下invokeAwareMethods方法中会调用Aware接口的那些方法
![](https://filescdn.proginn.com/6c09abd457755d5cef4a6adb48582f9c/ab5e9a1b35058d7378aa5b0099695f6d.webp)
我们可以知道如果我们实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware三个Aware接口的话,会依次调用setBeanName(), setBeanClassLoader(), setBeanFactory()方法,再看applyBeanPostProcessorsBeforeInitialization源码
![](https://filescdn.proginn.com/f0a6f58a1793ee799902aead3924b962/d44826c434e2adf99935e20563ccadf0.webp)
发现会如果有类实现了BeanPostProcessor接口,就会执行postProcessBeforeInitialization方法,这里需要注意的是:如果多个类实现BeanPostProcessor接口,那么多个实现类都会执行postProcessBeforeInitialization方法,可以看到是for循环依次执行的,还有一个注意的点就是如果加载A类到spring容器中,A类也重写了BeanPostProcessor接口的postProcessBeforeInitialization方法,这时要注意A类的postProcessBeforeInitialization方法并不会得到执行,因为A类还未加载完成,还未完全放到spring的singletonObjects一级缓存中。
最新面试题整理好了,大家可以在Java面试库小程序在线刷题。
再看一个注意的点
![](https://filescdn.proginn.com/3e5eebc9b38562ab5b3931a1eb1218d3/fbb5e95cef365db33542df62ae3a69e6.webp)
![](https://filescdn.proginn.com/d17ac94a1af25ca2c1884da1b6c49ef5/d16a5353e68f2ce3e8c98fdf129bfa7e.webp)
可以看到ApplicationContextAwareProcessor也实现了BeanPostProcessor接口,重写了postProcessBeforeInitialization方法,方法里面并调用了invokeAwareInterfaces方法,而invokeAwareInterfaces方法也写着如果实现了众多的Aware接口,则会依次执行相应的方法,值得注意的是ApplicationContextAware接口的setApplicationContext方法,再看一下invokeInitMethods源码
![](https://filescdn.proginn.com/43fb28a50bec759f57c62746518b4878/9eb37195d2acf0c9a891aca70e4f573b.webp)
发现如果实现了InitializingBean接口,重写了afterPropertiesSet方法,则会调用afterPropertiesSet方法,最后还会调用是否指定了init-method,可以通过
![](https://filescdn.proginn.com/8caa0e901acf157968d0e2a660c5fa18/628a912308c055170b62965360f4f2f6.webp)
发现跟之前的postProcessBeforeInitialization方法类似,也是循环遍历实现了BeanPostProcessor的接口实现类,执行postProcessAfterInitialization方法。整个bean的生命执行流程就如上面截图所示,哪个接口的方法在哪里被调用,方法的执行流程。
点击关注公众号,Java干货及时送达
最后,对bean的生命流程进行一个流程图的总结
![](https://filescdn.proginn.com/d1a73ed7e6988f843bce5393cc9b97ba/8c93e8e8cb16cb7c47f36c0a0c8761ba.webp)
三级缓存解决循环依赖
上一小节对bean的生命周期做了一个整体的流程分析,对spring如何去解决循环依赖的很有帮助。前面我们分析到填充属性时,如果发现属性还未在spring中生成,则会跑去生成属性对象实例。
最新面试题整理好了,大家可以在Java面试库小程序在线刷题。
![](https://filescdn.proginn.com/ca57440836361de47030b97d25f7073c/d1d267c3e73808a3b32ef11aa0d1acc7.webp)
我们可以看到填充属性的时候,spring会提前将已经实例化的bean通过ObjectFactory半成品暴露出去,为什么称为半成品是因为这时候的bean对象实例化,但是未进行属性填充,是一个不完整的bean实例对象
![](https://filescdn.proginn.com/9a20fe134b103084946f066b56a75fb2/56a0f4debbfdd0dc82242dc65cf3cd50.webp)
spring利用singletonObjects, earlySingletonObjects, singletonFactories三级缓存去解决的,所说的缓存其实也就是三个Map
![](https://filescdn.proginn.com/46ed5e1372a4ecabc9c9fc69e8c31f31/952104a2c6b1eefb539fd6e5b18a1d31.webp)
可以看到三级缓存各自保存的对象,这里重点关注二级缓存earlySingletonObjects和三级缓存singletonFactory,一级缓存可以进行忽略。前面我们讲过先实例化的bean会通过ObjectFactory半成品提前暴露在三级缓存中
![](https://filescdn.proginn.com/118c6deca04c9e88f857e19102f07b34/143e396f57b782b4fcaebbdbe45afa51.webp)
singletonFactory是传入的一个匿名内部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法。再来看看循环依赖中是怎么拿其它半成品的实例对象的。
我们假设现在有这样的场景AService依赖BService,BService依赖AService
1. AService首先实例化,实例化通过ObjectFactory半成品暴露在三级缓存中
2. 填充属性BService,发现BService还未进行过加载,就会先去加载BService
3. 再加载BService的过程中,实例化,也通过ObjectFactory半成品暴露在三级缓存
4. 填充属性AService的时候,这时候能够从三级缓存中拿到半成品的ObjectFactory
![](https://filescdn.proginn.com/48c86fff79291c29721095092e53f5f8/797fd13568a01bfb208b5f6204097fd2.webp)
下面是重点,我们发现这个二级缓存好像显得有点多余,好像可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题???
![](https://filescdn.proginn.com/1df1bbab14b6a51ae4220c82262543fa/e303848e9257a8f9482a9075274b8283.webp)
我们发现AService的testAopProxy被AOP代理了,看看传入的匿名内部类的getEarlyBeanReference返回的是什么对象。想成为架构师,这份架构师图谱建议看看,少走弯路。
![](https://filescdn.proginn.com/3e6714c2d291374800eb44ea820bc22f/21edf3b36b3279e02169e70c121f3fa3.webp)
发现singletonFactory.getObject()返回的是一个AService的代理对象,还是被CGLIB代理的。再看一张再执行一遍singletonFactory.getObject()返回的是否是同一个AService的代理对象
![](https://filescdn.proginn.com/81cacab4c2c9c05c9688610bd2967bc4/53cd461a31450ae545afc2f2e489a5c0.webp)
我们会发现再执行一遍singleFactory.getObject()方法又是一个新的代理对象,这就会有问题了,因为AService是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象。
假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,这是不行的,因为AService是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。还有一个注意的点
![](https://filescdn.proginn.com/faaee28df4d0f044df71bef5c06695e6/f1ea17e01bc953fbdb62b846a48265b7.webp)
既然singleFactory.getObject()返回的是代理对象,那么注入的也应该是代理对象,我们可以看到注入的确实是经过CGLIB代理的AService对象。所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象
总结
前面先讲到bean的加载流程,了解了bean加载流程对spring如何解决循环依赖的问题很有帮助,后面再分析到spring为什么需要利用到三级缓存解决循环依赖问题,而不是二级缓存。网上可以试试AOP的情形,实践一下就能明白二级缓存为什么解决不了AOP代理的场景了
在工作中,一直认为编程代码不是最重要的,重要的是在工作中所养成的编程思维。
![](https://filescdn.proginn.com/8d368d9c36bcd57851024366517253df/837f4dd77193cba45dbbe2164656ff63.webp)
关注Java技术栈看更多干货
![](https://filescdn.proginn.com/a79edf008a965fb7e82b38f5e241acba/cec04d165d8d0664c4b1d181fece9572.webp)