当前位置: 首页 > 科技观察

吐血给女朋友解释Spring循环依赖

时间:2023-03-13 23:14:50 科技观察

前言之前看了好几篇Spring的文章,了解了核心知识点IOC和AOP,还有事务传播。不知道大家有没有听说过Spring的循环依赖。是不是这个问题,这个问题在面试中经常被问到。属于Spring比较重要的一个话题,也比较典型。很考验一个人对Spring的研究水平,也是Spring的高阶题之一。一些小伙伴对Spring的内心看法可能是觉得自己看懂了,但是如果让自己给新手描述,有些人可能就说不出来了。本文主要是帮助大家解决这个痛点,让大家彻底了解它。去掉Spring的循环依赖,下次就不愁了。相反,在面试前张开嘴看一看。你可以很容易地接受这个提议。不注意还等什么?50K月薪等着你。如果你正在为选择哪个offer而苦恼,可以来和我分享你的好消息。循环依赖的问题。本文将从三个方面简单介绍一下:1、什么是Spring循环依赖2、各种情况下的循环依赖3、Spring是如何解决循环依赖的?了解Spring循环依赖。什么是循环依赖?一种循环依赖,彼此之间更多的相互依赖,比如A依赖B,B依赖C,C依赖A,类似三角恋……当然也有特殊的,自己对自己的依赖//我自己靠@ComponentpublicclassAService{//A注入到A@AutowiredprivateAServiceaService;}关于上面service的Springbean的创建,其实本质上也会创建一个对象。既然是对象,那肯定要明白,需要一个对象来完成的对象包含两部分:如果我们用常人的思维来考虑当前对象的实例化和对象属性的实例化,这肯定是不可能的.A需要B,B需要A,这不是死循环,和死锁一样,现在这个很尴尬,怎么解决,再看看很多情况下的循环依赖spring的循环依赖也可能出现在很多情况下,比如比如constructor注入,setter注入等,Spring在什么情况下可以解决循环依赖,什么情况下不能解决?Spring解决循环依赖的前提是:1.循环依赖的bean必须是单例2.依赖注入的方式不能全是构造函数注入。请注意,它不能是所有这些词。这里需要强调的是,大家可能会看到很多关于Spring解决循环依赖的博客。其中只有setter注入可以解决。说法是错误的。只要不在Spring中注入构造函数,就可以解决Springbean的创建。其实本质上会创建一个对象。既然是对象,就必须明白,一个对象需要完成。对象由两部分组成:当前对象对象的实例化和对象属性的实例化。在Spring中,对象的实例化是通过反射来实现的,对象的属性是在对象实例化之后通过一定的方法来设置的。知道了这一点,就可以更好的帮助大家理解上面提到的第一点一定是单例。其实很好理解。你想想,如果有多个instance,你不知道要引入哪个instance。Spring框架会被混淆。让我们大胆猜测一下,Spring以后可能会尝试解决这个问题。大概率是通过配置告诉注入哪一个。但是第二点,不能都是构造函数注入,先看代码@ComponentpublicclassAService{//@Autowired//privateBServicebService;publicAService(BServicebService){}}@ComponentpublicclassBService{//@Autowired//privateAServiceaService;publicBService(AServiceaService){}}上面的例子你就明白了,别告诉我你不明白,AService中的BService注入是通过该结构如果项目中有两个这样的引用,启动时会直接抛出异常Causedby:org.springframework.beans.factory。BeanCurrentlyInCreationException:创建名称为“a”的bean时出错:Requestedbean当前正在创建:Isthereanunresolvablecir引用?解决循环依赖首先,Spring内部维护了三个map来解决循环依赖,也就是我们常说的三级缓存。不知道你有没有听说过。之所以叫三级缓存,大概是因为上面使用了Cache这个注解,作用类似于缓存。1.singletonObjects:这是一个单例容器池,缓存创建一个单例bean。2.singletonFactories:用来映射创建bean的原始工厂。3.earlySingletonObjects:用来映射Bean的早期引用,也就是说这个Map中的Bean并不完整,是一个半成品,甚至不能称为Bean。只不过一个Instance后面的两个Map其实是在Product这个过程中消费的,什么意思,创建bean的时候需要暂时存放在这里,完成后再清空。我们先来看看创建过程。这个我大致分为四步,比较清楚。大家也很容易理解:1.A找不到B:在创建AService的过程中,发现需要BService,所以AService会去寻找BService,但是找不到。寻找的路径是一级缓存,二级缓存,三级Cache,所以AService把自己放在三级缓存2.B找到A,找到:实例化BService。在实例化过程中,发现需要AService,所以也按照上面的路径查找,在三级缓存中找到了AService3和B。创建:然后取出三级缓存中的AService,放入二级缓存中,删除三级缓存中的AService。此时BService可以被成功引用,成功初始化,将自己放入一级缓存(此时BService中的AService还处于正在创建状态。4.A被创建:继续完善AService,然后这时候去找BService,拿出来直接引用,把自己放到一级缓存,删除二级缓存,删除创建的状态信息。问题本质有点类似twosum的本质,这是leetcode上序号为1的题目,题目内容是:给定nums=[2,7,11,15],target=9,则返回[0,1],因为2+7=9的解题思路是利用Map,先去Map中找到需要的数,如果没有就把当前的数放在里面,有的如果想直接获取和一起返回,类似于三级缓存的本质,先去缓存中找到想要的Bean,如果你找到一切,一切都会好起来的,只需创建它并返回。找不到就放进去,简单一起分析源码。不要读太多,不要担心。所以会先创建Bean,再放入缓存。再次调用GetBean方法后,会直接从缓存中获取,不需要重新创建缓存。添加过程主要发生在Bean的创建过程中,即doCreateBean()过程。从代码中可以看出,beanName被放入三级缓存,从二级缓存中移除。这里的addSingletonFactory发生在createBeanInstance之后。这时候已经有一个实例对象了,只是在创建之前放入了三级缓存。doCreateBean()方法执行完成后,创建了Bean实例,所以如果在第二次getSingleton()的finally块中是新的单例对象,就会调用addSingleton()方法。可以看到此时会增加一级缓存,并从二级缓存和三级缓存中移除。通过getSingleton(StringbeanName)方法获取缓存,其源码如下:代码中,首先从一级缓存singletonObjects中获取Bean;从一级缓存中获取earlySingletonObjects如果二级缓存中还没有获取bean,允许从三级缓存中获取bean,则从三级缓存中获取bean。从缓存中移除,再加入二级缓存(缓存升级),否则返回null。最后问一个灵魂的问题,为什么不使用二级缓存,而是使用三级缓存呢?如果创建的Bean有对应的代理,那么在注入其他对象时,也要注入对应的代理对象;但是Spring无法提前知道这个对象是否有循环依赖,一般情况下(没有循环依赖),Spring会在创建完成品Bean后创建相应的代理。这时Spring有两个选择:是否存在循环依赖,提前创建代理对象,并将代理对象放入缓存。当发生循环依赖时,其他对象可以直接获取代理对象并注入。代理对象不是预先创建的,代理对象是在其他对象注入循环依赖时实时生成的。这样,在没有循环依赖的情况下,就可以按照Spring设计原则的步骤来创建bean了。Spring选择了第二种方式,那么如何在不生成代理的情况下提前暴露对象呢?Spring就是在对象外面包裹了一层ObjectFactory。ObjectFactory对象是提前暴露出来的,注入的时候只是在ObjectFactory.getObject方法中。实时生成代理对象,并将生成的代理对象放入二级缓存Map