今天在公交车上看到一篇关于Spring面试题的文章,里面有一个Spring循环依赖的问题,因为这是一个很常见的问题,但是这篇文章里面关于Spring循环依赖的问题讲解的确很简单,所以就深入研究了一下,就发现有个疑问一直没有弄懂。导致今天一天都在搞清楚这个问题,工作都没有开始做的。
Spring面试文章的链接是https://blog.csdn.net/qq_39390545/article/details/118905918?spm=1001.2014.3001.5501
一.循环依赖如何解决?
利用三级缓存来解决循环依赖。
springbean的初始化过程分为三步
1.调用构造方法实例化。
2.注入属性值。
3.初始化,初始化包括下面的相关过程
实现相关Aware接口,调用这些接口的重写方法。
实现BeanPostProcessor接口,重写了postProcessorBeforeInitialization方法,就执行postProcessorBeforeInitialization方法
实现了InitialzingBean接口,执行afterPropertiesSet方法
执行init-method方法
执行BeanPostProcessor的postProcessorAfterInitialization方法。
假设A依赖B,B依赖A。初始化A
在初始化A的时候,会先实例化A,然后将A存入到第三级缓存中。然后填充属性B,但发现B没有被初始化,就会去初始化B,B的初始化也是先实例化B,存入到三级缓存中,填充属性A,会先从一级缓存中查找,没有查找到,再从二级缓存中查找,还是没有查找到,就会从三级缓存中查找,查找到了A,然后会从三级缓存中取出存入二级缓存,并从三级缓存中移除,返回给B,这个时候B初始化完成,初始化完成后会将B存入一级缓存,然后删除二三级缓存中的B,再接着初始化A,A从一级缓存中取到了B,将B注入,然后初始化A,初始化A完成后会将A放入到一级缓存中,并删除二三级缓存中的A。
可以看出关键在于A还没有初始化完成的时候,先在A实例化了后放入到了三级缓存中,然后才去注入属性,这样在B完成初始化的时候能从三级缓存中获取到B。
看一下创建Bean的源码
AbstractAutowireCapableBeanFactory类的doCreateBean方法是创建bean的开始,我们可以看到首先需要实例化这个bean,也就是在堆中开辟一块内存空间给这个对象,createBeanInstance方法里面逻辑大概就是采用反射生成实例对象,进行到这里表示对象还并未进行属性的填充,也就是@Autowired注解的属性还未得到注入
我们可以看到第二步就是填充bean的成员属性,populateBean方法里面的逻辑大致就是对使用到了注入属性的注解就会进行注入,如果在注入的过程发现注入的对象还没生成,则会跑去生产要注入的对象,第三步就是调用initializeBean方法初始化bean,也就是调用我们上述所提到的接口
三级缓存中每一级缓存存储内容:
名称 | 描述 |
---|---|
singletonObjects | 一级缓存,存放完整的 Bean。 |
earlySingletonObjects | 二级缓存,存放提前暴露的Bean,Bean 是不完整的,未完成属性注入和执行 init 方法。 |
singletonFactories | 三级缓存,存放的是 Bean 工厂,主要是生产 Bean,存放到二级缓存中。 |
其实想一想我们在实例化好了后,将Bean工厂放入到三级缓存中去,不要二级缓存,然后取的时候从一级缓存中取,发现没有,再从三级缓存中获取到Bean工厂,通过Bean工厂获取到实例好像也是可以的。这里有个问题就是假设A被AOP代理了,也就是A的某一个方法作为切入点了,那么通过Bean工厂获取到的A对象其实是一个代理对象,而且每次获取到的代理对象都不是同一个,这就和我们spring中将bean设计成单例模式是相矛盾的。所以不能去掉中间的二级缓存。
然后还有一个问题就是
答案也在评论中
其实是可以将通过ObjectFactory创建好的对象放入到二级缓存中的,如果这样做的话,每次创建这个Bean不管这个Bean有没有被循环依赖,都会去执行getObject方法,但是这里用空间换了时间,存入ObjectFactory对象,在需要的时候才去执行ObjectFactory的getObject方法。什么时候需要,就是循环依赖的时候才需要
二级缓存是可以解决循环依赖的,spring引入三级缓存,是利用第三级缓存返回动态代理对象。一般会有一个疑问能否直接调用ObjectFactory的getObject方法返回动态代理,存入二级缓存,这样就可以丢掉三级缓存的疑问?其实这样是可以的,但是用到三级缓存就是利用空间换时间,因为循环依赖不是很常见,只有在真正循环依赖的时候,才会去三级缓存中查找对象,如果没有循环依赖的话,我们还要调用ObjectFactory的getObject方法获取对象放入三级缓存的话,我们创建完对象是直接放入到一级缓存中的,这个时候二级缓存里面的东西就没有用了,相对于放入三级缓存中的是ObjectFactory,然后不执行ObjectFactory的getObject方法来讲,少了一步执行ObjectFactory的getObject方法。
最后总结一下是这样的
1.一级缓存是IOC容器,我们可以在一级缓存中获取到完整的对象。
2.二级缓存存储的是没有注入属性和初始化的对象,解决的是每次通过ObjectFactory的getObject对象获取新的对象不一样的,符合单例模式,二级缓存解决的是循环依赖中的AOP代理问题。那有这么一种场景,B 和 C 都依赖了 A。而多次调用 singletonFactory.getObject()
返回的代理对象是不同的,就会导致 B 和 C 依赖了不同的 A。
3.三级缓存存储的是ObjectFactory对象,如果这个Bean引入了AOP的话,ObjectFactory的getObject方法返回的AOP对象,解决的是循环依赖的问题。
然后我在弄懂Spring循环依赖的问题中有两篇很好的文章,分别是: