本文转载自微信公众号“bugstack虫洞栈”,作者小傅。转载本文请联系bugstack公众号。目录1.前言2.面试题3.什么是循环依赖?1.问题描述2.问题表现3.问题处理4.源码分析1.细节2.处理过程3.依赖分析5.总结6.系列推荐1.前言延迟满足能给你带来什么?大学有四年,但几乎每个人都在临近毕业前,很难找到一份好工作,尤其是我非常熟悉的软件开发行业,即使毕业后,也需要额外花钱才能找到一份好工作。去培训机构,学好编程技能,才能出去找工作。看来自己在学校的这几年根本就没有学到什么啊!个人而言,可能是因为在校期间喜欢编程,也听师兄师姐说毕业后找工作难,也了解了一些社会问题。所需的程序员开发技能水平。也就是得到这个消息后,加上自己愿意折腾,给自己定了一个每天能完成的小目标:红尘中几位王者,我是不会正面接受的.每天敲200行代码,闯入世界500强。哈哈哈,一天200行代码,一个月6000行,一年60000行,实习三年18万行,一个大一实习生将近20万行代码,几乎可以很熟练的完成各种简单的任务,而且加上实习期间整个项目过程真正脱节之后,还是很容易找到一份正经的开发工作的。这个时候找工作的轻松来自于你长期的学习和沉淀,但是如果你没有经过这些努力,你毕业后可能会变得很慌张。最后只好再去一些机构读书。2.面试题谢谢你的飞机,小记!以前总觉得Spring没什么。看了一篇关于getBean的文章,我的天!解决循环依赖,面试这个东西有什么想问的吗?面试官:对,哇,Spring是怎么解决循环依赖的?谢季飞:嗯,通过三级缓存提前暴露对象就解决了。面试官:当然可以,这三个缓存中存储了什么样的对象信息?谢继飞:一级缓存存放的是完整的对象,也叫成品对象。二级缓存存放的是半成品对象,即尚未赋值的对象。三级缓存存放的是ObjectFactory类型的lambda表达式,用于处理AOP循环依赖。采访者:是的,谢吉吉准备好了!那么如果没有三级缓存,只有level-2或者level-1,是否可以解决循环依赖呢?谢冀冀:其实看了资料也可以解决,但是Spring需要保证一些东西,只有一级缓存处理过程不能拆分,复杂度会增加。同时,半成品对象可能出现空指针异常。通过分离半成品和成品对象,它也更加优雅、简单和易于扩展。另外,Spring的两大特点不仅有IOC还有AOP,就是基于增强的字节码方式,应该存储在哪里,三级缓存是最重要的,要解决的循环依赖是AOP的处理,但是如果提前创建AOP代理对象的话,二级缓存也可以解决。但是,这违反了Spring中创建对象的原则。Spring更喜欢初始化所有普通bean并处理代理对象的初始化。面试官:飞机,不错,这次学到了很多东西。让我问你一个简单的问题。您是否尝试过循环依赖的解决方案?谢季飞:哦,没有,我没试过!!!你真的应该这样做并尝试一下。3.什么是循环依赖?1.问题描述了解问题的本质,进而分析问题,往往更有利于对问题进行更深入的理解和研究。所以在我们分析Spring关于循环依赖的源码之前,首先要了解什么是循环依赖。循环依赖分为三种类型,自依赖、相互循环依赖和多组循环依赖。但是不管循环依赖有多少,循环依赖的本质是一样的。即你的完整创作靠我,我的完整创作也靠你,但我们不能相互解耦,最终导致依赖创作的失败。所以Spring除了构造函数注入和原型注入之外,还提供了setter循环依赖注入方案。那我们也可以先试试这样的依赖,自己处理怎么解决。2.问题体现在publicclassABTest{publicstaticvoidmain(String[]args){newClazzA();}}classClazzA{privateClazzBb=newClazzB();}classClazzB{privateClazzAa=newClazzA();}这段代码是循环的初始出现依赖。有我,我中有你,运行时会报java.lang.StackOverflowError之类的循环依赖代码。当看到Spring中提供了get/set或者注解的时候,之所以能解决,首先是。一定程度的脱钩。将类的创建和属性的填充分开,先创建一个半成品bean,然后处理属性的填充,完成成品bean的提供。3.问题处理这部分代码有一个核心目的。让我们自己解决循环依赖。解决方法如下:System.out.println(getBean(A.class).getB());}privatestaticTgetBean(ClassbeanClass)throwsException{StringbeanName=beanClass.getSimpleName().toLowerCase();if(singletonObjects.containsKey(beanName)){return(T)singletonObjects.get(beanName);}//实例化对象入缓存Objectobj=beanClass.newInstance();singletonObjects.put(beanName,obj);//属性填充完成对象Field[]fields=obj.getClass().getDeclaredFields();for(Fieldfield:fields){field.setAccessible(true);Class>fieldClass=field.getType();StringfieldBeanName=fieldClass.getSimpleName().toLowerCase();field.set(obj,singletonObjects.containsKey(fieldBeanName)?singletonObjects.get(fieldBeanName):getBean(fieldClass));field.setAccessible(false);}return(T)obj;}}classA{privateBb;//...get/set}classB{privateAa;//...get/set}这段代码提供了两个类A和B,它们相互依赖,但是使用了两个类中的依赖是setter的填充方式。也就是说,只有这样才能避免两个类在创建之初就不必强依赖另一个对象。getBean是整个解决循环依赖的核心内容。A创建后,在填写属性时依赖于B。然后创建B,当B创建完成开始填充时,发现它依赖于A。但是此时A的半成品对象已经存放在缓存中的singletonObjects中,所以B可以正常创建,A也是通过递归创建的。四、源码分析1.说说细节通过上面的例子,我们可以大致了解到,当A和B相互依赖时,创建A后,填写属性B,继续创建B,再填写属性A,那么就可以从缓存中获取。如下:那么解决循环依赖这件事情在Spring中是什么样子的呢?展开详情!虽然解决循环依赖的核心原理是一样的,但是当它用于支持整个Spring中的IOC和AOP特性时,就会变得更加复杂。Spring处理循环依赖的整个过程如下;以上就是Spring中循环依赖对象的获取过程,也就是你要讲的细节。乍一看,流程挺多的,其实这些基本都是你的了。非常方便的获取调试代码时必须经过的代码片段的执行流程,然后进行调试。2、处理过程关于本章涉及案例的源码分析,已经更新到github:https://github.com/fuzhengwei/interview-interview-31下面是获取依赖AB的Bean的操作单元测试中,重点进入GetBean源码后续;@Testpublicvoidtest_alias(){BeanFactorybeanFactory=newClassPathXmlApplicationContext("spring-config.xml");Bean_Abean_a=beanFactory.getBean("bean_a",Bean_A.class);logger.info("通过别名获取Bean:{}",bean_a.getBean_b());}org.springframework.beans.factory.support.AbstractBeanFactory.java@OverridepublicTgetBean(Stringname,ClassrequiredType)throwsBeansException{returndoGetBean(name,requiredType,null,false);}进入from后getBean,获取bean的操作会进入doGetBean。之所以这样包装是因为doGetBean有很多不同入参的重载方法,方便外部操作。doGetBean方法protectedTdoGetBean(finalStringname,finalClassrequiredType,finalObject[]args,booleantypeCheckOnly)throwsBeansException{//从缓存中获取bean实例ObjectsharedInstance=getSingleton(beanName);//mbd.isSingleton()用于判断bean是否为单例模式if(mbd.isSingleton()){//获取bean实例sharedInstance=getSingleton(beanName,newObjectFactory