6

Spring循环依赖的“夺命连环问”,你能招架几波?

 2 years ago
source link: https://developer.51cto.com/article/704835.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

e9b64f77357ce0112a2242cb54e782472921be.png

大家好,欢迎来到Tlog4J课堂,我是Jensen。

面试官:Spring是如何解决循环依赖问题的?

候选人:Spring用了三级缓存来解决这个问题

面试官:能具体讲讲它的工作原理吗?

候选人:啊这……

Spring循环依赖其实是Spring当中非常典型的一个问题,也非常难的一道题,因为回答这个问题本身会特别绕,而且这不仅仅是一道题这么简单,它后面会引发面试官一系列的夺命连环问。

那今天咱一起把循环依赖涉及的问题认真梳理一遍。

Spring循环依赖夺命连环问

一问:什么是循环依赖?

循环依赖指的是多个对象之间的依赖关系形成一个闭环。

我们都知道,如果在代码中,把两个或者多个Bean相互之间去持有对方的引用,就会发生循环依赖,循环依赖会导致注入出现死循环,这是Spring发生循环依赖的一个原因。

二问:Spring的循环依赖有哪几种形态?

第一种是互相依赖,也就是A依赖B,B又依赖A,它们之间形成了一个循环依赖:

6639451175931b4878e160550eac11b242fa11.png

第二种是三者之间的依赖,也就是A依赖B,B依赖C,C又依赖A,形成了一个循环依赖:

14de31f508604d48d53032ec1951631bd13f99.png

第三种是自我依赖,也就是说A与A之间形成的循环依赖。

现实中由于依赖层次深、关系复杂等因素, 导致循环依赖可能并不是那么一目了然。

三问:Spring设计三级缓存分别存放什么内容?

三级缓存说白了就是三个Map容器,在框架里头这种Map容器是随处可见:

第一层: 初始化完备的单例bean。

/** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new 
ConcurrentHashMap<>(256);

第二层: 提前曝光的单例对象的Cache。

/** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new 
HashMap<>(16);

第三层: ObjectFactory工厂bean缓存, 存储实例化后的bean Factory。

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletonFactories = new 
HashMap<>(16);

四问:Spring这个三级缓存具体的工作原理是什么?

循环依赖主要是解决这个“死循环”没有“出口”的问题,只要我们找到这个“出口”,循环依赖就能迎刃而解。

我们分四个步骤来说:

  • 当我们通过getBean(A.class)去获得A对象实例的时候,Spring会先去一级缓存里边找,如果发现一级缓存没有找到BeanA,就去创建这个A的实例,添加到三级缓存并暴露Bean。
  • 这时候通过@Autowired依赖注入BeanB的时候,也就是getBean(B.class)的时候,Spring发现B在一级缓存找不到,于是B又重复第一步A的步骤,创建这个B的实例,添加到三级缓存并暴露Bean。
  • B通过@Autowired依赖注入BeanA的时候,这时候发现三级缓存找到了A的实例,这时候A如果实现AOP会先创建动态代理对象,然后把A从三级缓存移入了二级缓存中,至此B依赖注入成功,并完成了初始化工作,随即Spring把B从三级缓存移到了一级缓存里头,B就完成了整个的初始化工作。
  • 最后Spring加到了A的创建周期,A依赖注入B也就完成了,然后继续完成A的初始化工作,随即把A从二级缓存移到一级缓存。

这里我们可以做一个总结:

796301b239e87c0d161245c0943371b9367599.png

如果还是不理解,可以多看几遍这个动图:

6746f6839bcef9e1090583446ae711e5efa226.gif

一句话概括:先让最底层对象完成初始化,通过三级缓存与二级缓存提前曝光创建中的Bean,让其他Bean率先完成初始化。

五问:Spring在哪些情况下是无法解决循环依赖问题的呢?

有四种情况是无法解决的:

  1. 多实例Bean通过Setter注入。
  2. 通过构造器注入Bean。
  3. 单例的代理Bean通过Setter注入。
  4. 设置@DependsOn注解的Bean。

其实,很多同学回答这个问题的时候也都知道,Spring就是使用三级缓存来处理这个问题,但是回答这个问题的难点在于,很多同学对这个问题理解不透,所以当你想要在短时间内向面试官解释清楚这个问题的时候,就会觉得有点条理不清。

以上这些都是从底层原理升华而来的具体问题,通常互联网大厂面试都是按这个套路,包括说很多基础知识像JVM、NIO、MQ等等这些也都是一样的——面试官先从框架的工作原理入手,考察你对框架原理的理解,最后再找一些实际的问题,考你对这种技术栈的综合的掌握能力,能不能解决实际的问题。

OK,回答好这个问题,关乎于你对Spring框架是否有深刻的理解,相信大家再次总结完后会有所收获。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK