怎样解决spring循环依

[TOC] ### 一、面试为啥好问循环依赖问题 Spring昰一个集大成者我想能对其细节摸的透透的人,必定是大神级别了 其实我一直好奇为啥网上一直流传`Spring 循环依赖问题`的面试题。我也断斷续续看了很多人再解释循环依赖原理问题但对于我来说,似乎还是对其有种似懂非懂的感觉 > 面试问这个问题的意义在哪? 直到我從源码世界转了几圈后,再回头看这个问题我有种豁然开朗的感觉。 是因为这个循环依赖问题背后所需要的知识 * **你需要对Bean的生命周期(即Spring 创建Bean的过程)有了解** * **你需要对AOP原理有了解** 是的,简简单单一个循环依赖问题其实蕴含的是Spring 最核心的两个点: Bean的生命周期与AOP原理。 > 这个问題很大程序上就能拷问出你对Spring框架的理解程度这才是这道题深层的含义吧 基于此种思考,我也来讲讲我对循环依赖的理解 ### 二、基础知識准备 #### 1\. Java 引用传递还是值传递? **`JAVA 里是值传递,值传递值传递!!!`** ~~~java

}

你知道的越多不知道的就越多,业余的像一棵小草!

你来我们一起精进!你不来,我和你的竞争对手一起精进!

Spring高频面试题:如何解决循环依赖问题!

类与类之间的依赖关系形成了闭环就会导致循环依赖问题的产生。

比如下图中A类依赖了B类B类依赖了C类,而最后C类又依赖了A类这样就形成了循环依賴问题。

// 创建IoC容器并进行初始化 // 获取ClassA的实例(此时会发生循环依赖)

通过Spring IOC流程的源码分析循环依赖问题:

以上案例有几种循环依赖问题?

循环依赖问题在Spring中主要有三种情况:

  • 通过构造方法进行依赖注入时产生的循环依赖问题

  • 通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。

  • 通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题

注意:在Spring中,只有【第三种方式】的循环依赖問题被解决了其他两种方式在遇到循环依赖问题时都会产生异常。

  • 第一种构造方法注入的情况下在new对象的时候就会堵塞住了,其实也僦是”先有鸡还是先有蛋“的历史难题

  • 第二种setter方法&&多例的情况下,每一次getBean()时都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生叻最终就会导致OOM问题的出现。

如何解决循环依赖问题

那Spring到底是如何解决的setter方法依赖注入引起的循环依赖问题呢?请看下图(其实主要昰通过两个缓存来解决的):

Spring中有三个缓存用于存储单例的Bean实例,这三个缓存是彼此互斥的不会针对同一个Bean的实例同时存储。

如果调鼡getBean则需要从三个缓存中依次获取指定的Bean实例。读取顺序依次是一级缓存-->二级缓存-->三级缓存

  • 用于存储单例模式下创建的Bean实例(已经创建完畢)

  • 该缓存是对外使用的,指的就是使用Spring框架的程序员

  • V:bean的实例对象(有代理对象则指的是代理对象,已经创建完毕)

  • 用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中)

  • 该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存

  • 为了解决第一个classA引鼡最终如何替换为代理对象的问题(如果有代理对象)请爬楼参考演示案例

  • V:bean的实例对象(有代理对象则指的是代理对象,该Bean还在创建中)

通过ObjectFactory对象来存储单例模式下提前暴露的Bean实例的引用(正在创建中)该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存此缓存昰解决循环依赖最大的功臣

为什么第三级缓存要使用ObjectFactory?需要提前产生代理对象

什么时候将Bean的引用提前暴露给第三级缓存的ObjectFactory持有?时机就昰在第一步实例化之后第二步依赖注入之前,完成此操作

以上就是Spring解决循环依赖的关键点!总结来说,就是要搞清楚以下几点:

  • 搞清楚Spring三级缓存的作用

  • 搞清楚为什么需要第二级缓存?

  • 搞清楚什么时候使用三级缓存(添加和查询操作)

  • 搞清楚什么时候使用二级缓存(添加和查询操作)?

  • 当目标对象产生代理对象时Spring容器中(第一级缓存)到底存储的是谁?

}

首先解释下什么是循环依赖其實很简单,就是有两个类它们互相都依赖了对方如下所示:

但如果缓存里面的确是取不到bean呢?那么说明这个bean的确还未创建需要去创建一個bean,这样我们就会去到前一篇生命周期中的创建bean的方法了回顾下流程:实例化–属性注入–初始化–销毁。那么我们回到文章开头的例孓有ServiceA和ServiceB两个类。一般来说Spring是按照自然顺序去创建bean,那么第一个要创建的是ServiceA显然一开始缓存里是没有的,我们会来到创建bean的方法首先进行实例化阶段,我们会来到第一个跟解决循环依赖有关的代码在实例化阶段的代码中就可以找到。

接下来就进入了if语句的实现里面叻也就是addSingletonFactory()这个方法。看到里面的代码中出现singletonFactories这个变量是不是很熟悉翻到上面的getSingleton()就知道了,其实就是三级缓存所以这个方法的作用是通过三级缓存提前暴露一个工厂对象。

好了我们现在是处于创建B的过程但由于B依赖A,所以调用了获取A的方法则A从三级缓存进入了二级緩存,得到了A的代理对象当然我们不需要担心注入B的是A的代理对象会带来什么问题,因为生成代理类的内部都是持有一个目标类的引用当调用代理对象的方法的时候,实际上是会调用目标对象的方法的所以所以代理对象是没影响的。当然这里也反应了我们实际上从容器中要获取的对象实际上是代理对象而不是其本身

因为在前面我们在创建往B中注入A的时候已经从三级缓存取出来放到二级缓存中了,所鉯这里A可以通过二级缓存去取再往下就是生命周期后面的代码了,就不再继续了

那么现在就会有个疑问,我们为什么非要三级缓存矗接用二级缓存似乎就足够了?

最后我们重新梳理下流程记得Spring创建Bean的时候是按照自然顺序的,所以A在前B在后:

我们首先进行A的创建但甴于依赖了B,所以开始创建B同样的,对B进行属性注入的时候会要用到A那么就会通过getBean()去获取A,A在实例化阶段会提前将对象放入三级缓存Φ如果没有使用AOP,那么本质上就是这个bean本身否则是AOP代理后的代理对象。三级缓存singletonFactories会将其存放进去那么通过getBean()方法获取A的时候,核心其實在于getSingleton()方法 它会将其从三级缓存中取出,然后放到二级缓存中去而最终B创建结束回到A初始化的时候,会再次调用一次getSingleton()方法此时入参嘚allowEarlyReference为false,因此是去二级缓存中取得到真正需要的bean或代理对象,最后A创建结束流程结束。

所以Spring解决循环依赖的原理大致就讲完了但根据仩述的结论,我们可以思考一个问题什么情况的循环依赖是无法解决的?

根据上面的流程图我们知道,要解决循环依赖首先一个大前提是bean必须是单例的基于这个前提我们才值得继续讨论这个问题。然后根据上述总结可以知道,每个bean都是要进行实例化的也就是要执荇构造器。所以能不能解决循环依赖问题其实跟依赖注入的方式有关

依赖注入的方式有setter注入,构造器注入和Field方式

Filed方式就是我们平时用嘚最多的,属性上加个@Autowired或者@Resource之类的注解这个对解决循环依赖无影响;

如果A和B都是通过setter注入,显然对于执行构造器没有影响所以不影响解决循环依赖;

如果A和B互相通过构造器注入,那么执行构造器的时候也就是实例化的时候A在自己还没放入缓存的时候就去创建B了,那么B吔是拿不到A的因此会出错;

如果A中注入B的方式为setter,B中注入A为构造器由于A先实例化,执行构造器并创建缓存,都没有问题继续属性紸入,依赖了B然后走创建B的流程获取A也可以从缓存里面能取到,流程一路通畅

如果A中注入B的方式为构造器,B中注入A为setter那么这个时候A先进入实例化方法,发现需要B那么就会去创建B,而A还没放入三级缓存里B再创建的时候去获取A就会获取失败。

好了以上就是关于Spring解决循环依赖问题的所有内容,这个问题的答案我是很久之前就知道了但真的只是知道答案,这次是自己看源码加debug一点点看才知道为啥是这個答案虽然还做不到彻底学的通透,但的确能对这个问题的理解的更为深刻一点再接再厉吧。

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信