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

SpringBeanIOC和AOP循环依赖解读

时间:2023-03-13 08:39:59 科技观察

本文转载自微信公众号“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);ClassfieldClass=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(){@OverridepublicObjectgetObject()throwsBeansException{try{//创建bean实例,createBean返回的bean被实例化了);}//...//返回bean实例return(T)bean;}源码分析流程图可以看出,这部分是判断是否有来自getSingleton的实例对象,是肯定的对于第一个条目如果你没有对象,你应该继续不下去了。判断完mbd.isSingleton()的单例后,开始使用基于ObjectFactory的封装来创建createBean。进入后,核心逻辑就是开始执行doCreateBean操作。doCreateBeanmethodprotectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionmbd,finalObject[]args)throwsBeanCreationException{//创建bean实例并将bean实例包装到BeanWrapper对象中returninstanceWrapper=createBeanInstance(beanName,mbd,args);//添加bean工厂对象到singletonFactories在缓存中addSingletonFactory(beanName,newObjectFactory(){@OverridepublicObjectgetObject()throwsBeansException{//获取原始对象的earlyreference,在getEarlyBeanReference方法中,会执行AOP相关逻辑。如果bean没有被拦截通过AOP,getEarlyBeanReference按原样返回beanexposedObject=initializeBean(beanName,exposedObject,mbd);}}//返回bean实例returnexposedObject;}doCreateBean方法里面有很多内容,但是e核心主要是创建实例,添加缓存,最后填充属性。属性填充就是涉及到一个bean类的各个属性字段的填充。createBeanInstance,创建bean实例,并将bean实例包装成BeanWrapper对象返回addSingletonFactory,将bean工厂对象添加到singletonFactories缓存中执行。如果bean没有被AOP拦截,getEarlyBeanReference将按原样返回bean。populateBean,填充属性,解决依赖关系。即从这里开始寻找A实例中的属性B,然后创建B实例,最后返回。getSingletonL3cacheprotectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){//从singletonObjects中获取实例,singletonObjects是一个完成的beanObjectsingletonObject=this.singletonObjects.get(beanName);//判断beanName,isSingletonCurrentlyInCreation对应的bean是否正在创建if(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){synchronized(this.singletonObjects){//从earlySingletonObjects中获取未完成的beansingletonObject=this.earlySingletonObjects.get(beanName);if(singletonObject==null&&allowEarlyReference){//获取对应的beanfactoryObjectFactorysingletonFactory=this.singletonFactory.get(beanName);if(singletonFactory!=null){//提前暴露bean实例,主要用于解决AOP循环依赖singletonObject=singletonFactory.getObject();//放置singletonObject进入缓存并从缓存中移除singletonFactory.get(beanName),fromsingletonObjects获取实例,singletonObjects是完成的beanisSingletonCurrentlyInCreation,判断beanName,是否正在创建isSingletonCurrentlyInCreation对应的beanallowEarlyReference,从earlySingletonObjects中获取提前暴露未完成的beansingletonFactory.getObject(),提前暴露bean实例,主要用于解决AOP循环依赖总结起来就是一个处理循环依赖的代码过程。这部分提取的内容主要是核心内容,没有冗长的全部拆解。调试时你会涉及更多,你应该尽量按照流程进行。几次图运行调试3.依赖分析综上所述,我们尝试自己解决循环依赖,学习了循环依赖的核心解决原理。并分析了Spring解决循环依赖的处理过程及核心源码分析。那么我们就来总结一下三级缓存的不同处理过程,也算是一个总结,也方便大家理解。1、一级缓存能解决吗?其实只有一级缓存是解决不了循环依赖的,就像我们自己的例子一样。但是在Spring中,如果按照我们的例子来处理,就会变得很麻烦,而且还可能出现NPE问题。因此,根据图中Spring中的代码处理流程,我们可以分析出将完成的bean存放在一级缓存中的过程,并不能解决循环依赖的问题。因为A的成品创建依赖于B,而B的成品创建又依赖于A,当需要完成B的属性时,A还没有创建,所以会出现死循环。2、二级缓存能解决吗?有了二级缓存,其实处理这个事情就很简单了。一个缓存用于存储成品对象,另一个缓存用于存储半成品对象。A创建完一个半成品对象后,存放在缓存中,然后补充A对象依赖B的属性。B继续创建,创建的半成品也放入缓存中。在补充对象的A属性时,可以从半成品缓存中获取。现在B是一个完整的对象,然后A也是一个完整的对象就像递归操作一样。.3、三级缓存解决什么问题?有了二级缓存,就可以解决Spring依赖。怎么会有三级缓存呢?其实我们在前面分析源码的时候也提到过,三级缓存主要是为了解决SpringAOP的特点。AOP本身就是方法的增强,是ObjectFactory类型的lambda表达式,而Spring的原理是不想预先创建这种类型的Bean,所以必须存放在三级缓存中进行处理。其实整体的处理过程是类似的,只是B在填充属性A的时候,先查询成品缓存,然后查看半成品缓存,最后查看第三个中是否有单例项目类级缓存。最终获取后,调用getObject方法返回代理引用或原始引用。至此,解决了SpringAOP带来的三级缓存问题。本章涉及到的AOP依赖都有源码示例,可以调试https://github.com/fuzhengwei/interview五、总结与回顾本文基本都是从实例入手,引导大家对循环依赖有一个整体的认识,也可以用一个例子来说明一下它的解决方法,这样后面对Spring循环依赖的解决方法就不会那么陌生了。纵观全文,也可以看出三级缓存不是必须的,但是必须要符合Spring自己创建的原则。如果可以下载Spring源码修改这部分代码,提前创建一个AOP对象保存在缓存中,那么二级缓存也可以解决循环依赖问题。关于循环依赖,可能不是一个好的编码方式。如果你还是想在自己的程序中使用更合理的设计模式来避免循环依赖,这些方法可能会增加代码量,但维护起来会更方便。当然这不是强制的,可以根据自己的需要来。