当前位置: 首页 > 后端技术 > Java

Spring源码之Bean加载(三)

时间:2023-04-02 00:03:47 Java

bean加载(三)上一篇主要讲解了从bean实例中获取对象,准备过程以及实例化的预处理。实例化bean是一个非常复杂的过程。本文主要讲解Spring是如何解决循环依赖的。什么是循环依赖?循环依赖就是循环引用。事实上,两个或更多的豆子相互支撑。例如,A指B,B指C,C指A,最终成为环。循环依赖无法解决。除非有终止条件,否则会无限循环,直到内存溢出。什么情况下可以处理循环依赖?Spring中解决循环依赖是有前提条件的:循环依赖的bean必须是单例,如果是prototype则不会有依赖注入。依赖注入的方法不能都是构造函数注入,只能解决纯setter注入的情况依赖依赖注入的方法可以解决A和B的相互依赖都使用setter注入是A和B相互依赖都使用属性自动注入是A和B相互依赖useconstructorinjection没有A,B相互依赖可以使用constructorinjectionB是相互依赖的,A是作为setter注入的,B是构造函数。A和B是相互依存的。如何解决循环依赖首先,Spring容器循环依赖包括constructor循环依赖和setter循环依赖。在了解Spring如何解决循环依赖之前,我们首先创建这些类。公共类TestA{私有TestBtestB;publicTestA(TestBtestB){this.testB=testB;}publicvoida(){testB.b();}publicTestBgetTestB(){返回testB;}publicvoidsetTestB(TestBtestB){this.testB=testB;}}publicclassTestB{privateTestCtestC;publicTestB(TestCtestC){this.testC=testC;}publicvoidb(){testC.c();}publicTestCgetTestC(){returntestC;}publicvoidsetTestC(TestCtestC){this.testC=testC;}}publicclassTestC{privateTestAtestA;publicTestC(TestAtestA){this.testA=testA;}publicvoidc(){testA.a();}publicTestAgetTestA(){返回testA;}publicvoidsetTestA(TestAtestA){this.testA=testA;}}ConstructorcirculardependencyConstructorcirculardependency是指构造函数注入导致的循环依赖,需要注意的是这种情况无法解决,会抛出BeanCurrentlyInCreationException。Spring容器会将每一个正在创建的Bean标识放在一个“当前创建的Bean池中”,Bean标识在创建过程中会一直保留在这个池中。如果在创建bean的过程中发现自己已经在“正在创建bean池”中,则会抛出上述异常,说明存在循环依赖。当bean创建时,它会从“CurrentlyCreatedBeanPool”中移除。创建配置文件创建测试用例publicclassTest{publicstaticvoidmain(String[]args){newClassPathXmlApplicationContext("spring-config.xml");Causedby:org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'b'definedinclasspathresource[spring-config.xml]:Cannotresolvereferencetobean'c'同时设置构造函数参数;嵌套异常是org.springframework.beans.factory.BeanCreationException:创建类路径资源中定义的名为“c”的bean时出错[spring-config.xml]:设置构造函数参数时无法解析对bean'a'的引用;嵌套异常是org.springframework.beans.factory.BeanCurrentlyInCreationException:创建名称为“a”的bean时出错:当前正在创建请求的bean:是否存在无法解析的循环引用?如果我们理解刚才描述的情况,我们很容易想到,在创建TestC对象的时候,我们需要准备它的构造参数TestA。这时Spring容器准备创建TestA,却发现bean标识已经在“当前Createabeanpool”中,于是抛出上述异常。setter注入的setter循环依赖是由于Spring容器暴露了刚刚完成constructor注入但还没有完成其他步骤(比如setter注入)的bean通过Spring容器造成的。而且只能解决单例作用域下的bean循环依赖。通过提前暴露一个单例工厂方法,其他bean可以引用这个bean。按照我们的代码案例流程是这样的:Spring容器创建一个单例TestA,然后调用无参构造函数创建一个bean,并暴露ObjectFactory,用于返回一个预先暴露的bean,并放入“当前创建的bean池”中的testA标识符,然后将setter注入TestBSpring容器创建单例TestB,然后调用无参构造函数创建bean,暴露ObjectFactory返回一个被暴露的bean预先,并把testB标识放在“当前创建bean池”中,然后将setter注入TestCSpring容器创建单例TestC,然后调用无参构造函数创建bean,将ObjectFactory暴露给返回一个预先暴露出来的bean,并将testC标识放在“当前创建的bean池”中,然后将setter注入TestA。既然ObjectFactory已经提前暴露了,那么用它来返回一个创建时提??前暴露的bean。其余的都是一样的。原型作用域scope="prototype"的依赖处理,意味着每次请求都会创建一个实例对象。两者的区别是:statefulbeans使用Prototype作用域,statelessbeans一般使用singleton单例作用域。对于“prototype”作用域bean,Spring容器无法完成依赖注入,因为“prototype”作用域bean,Spring容器没有缓存,所以无法暴露一个正在提前创建的bean。所以还是会抛出上面的异常。流程分析,这里以TestA依赖TestB,TestB依赖TestA为例。TestA和TestB循环依赖的场景:当TestBpopulatedBean查找依赖TestA时,虽然没有从一级缓存中获取到TestA,但是发现正在创建TestA。此时从三级缓存中获取A的singletonFactory调用工厂方法,创建getEarlyBeanReferenceTestA的早期引用并返回。二级缓存能否解决循环依赖?我们知道,在实例化过程中,所有半成品对象的地址都放在缓存中,提前暴露对象。在后续的过程中,将预先暴露的对象再次赋值,然后将赋值完成的对象,即完成的对象放入一级缓存,删除二级缓存和三级缓存。如果不使用二级缓存,一级缓存中会有半成品和成品对象。获取时,可能会获取半成品对象,无法使用。如果不需要三级缓存,不使用AOP,只需要一级二级缓存就可以解决Spring循环依赖;但是如果使用AOP来增强功能,就必须使用三级缓存,因为在获取到三级缓存的过程中,会使用代理对象来替代非代理对象。如果没有三级缓存,则无法获取到代理对象。三级缓存是为了解决AOP代理过程中产生的循环依赖问题。我们在代码中加入aop相关的切面操作后,在initializeBean方法中产生变化,调用applyBeanPostProcessorsBeforeInitialization方法。getBeanPostProcessors中有一个processor:AnnotationAwareAspectJAutoProxyCreator其实是一个添加的注解切面,然后调用会跳转到AbstractAutoProxyCreator类的postProcessAfterInitialization方法。在下面的代码中,wrapIfNecessary方法会判断是否满足代理条件,如果满足则返回一个代理对象,否则返回当前Bean。最后TestA被替换为代理对象。代理对象由doCreateBean返回,放在一级缓存中。如果TestA和TestB都使用了AOP动态代理,那么前面的一系列流程和正常流程没什么区别。唯一不同的是,在创建TestB时,需要从三级缓存中获取TestA。这时候会在getSingleton方法中调用:singletonObject=singletonFactory.getObject();看到wrapIfNecessary你就会明白!这里会得到一个代理对象。也就是说,此时返回并放入二级缓存的是TestA的一个代理对象。这样,TestB就创建好了!当TestA开始初始化并执行后处理器时,因为TestA也有代理,所以TestA也会执行postProcessAfterInitialization部分!但是在执行wrapIfNecessary之前,它会先判断代理对象缓存中是否有TestA。但是这块是TestA的代理对象。一定是假的。所以不会再次生成TestA的代理对象。总结一下TestA和TestB都有动态代理的情况的流程图: