bean 的加载(三)
之前文章次要解说了从bean的实例中获取对象,筹备过程以及实例化的前置解决。实例化bean是一个非常复杂的过程,本文次要解说Spring是如何解决循环依赖。
什么是循环依赖
循环依赖就是循环援用,其实就是两个或者多个bean互相持有对方,比方 A 援用 B ,B 援用 C,C 援用 A,最终成为一个环。
循环依赖是无奈解决的,除非有终结条件,否则就是死循环,直到内存溢出。
什么状况下循环依赖能够被解决
Spring中解决循环依赖是有前置条件:
- 呈现循环依赖的bean必须是单例的,如果是prototype则不会呈现
- 依赖注入的形式不能全为结构器注入,只能解决纯setter注入的状况
依赖状况 | 依赖注入形式 | 是否能够解决 |
---|---|---|
A、B相互依赖 | 均采纳setter形式注入 | 能够 |
A、B相互依赖 | 均采纳属性主动注入 | 能够 |
A、B相互依赖 | 均采纳结构器注入 | 不能够 |
A、B相互依赖 | A中注入为setter,B中为结构器 | 能够 |
A、B相互依赖 | A中注入为结构器,B中为setter,Spring在创立过程中会依据天然排序,A优先于B创立 | 不能够 |
Spring如何解决循环依赖
首先Spring容器循环依赖包含结构器循环依赖和setter循环依赖,在理解Spring是如何解决循环依赖之前,咱们先创立这几个类。
public class TestA { private TestB testB; public TestA(TestB testB) { this.testB = testB; } public void a(){ testB.b(); } public TestB getTestB() { return testB; } public void setTestB(TestB testB){ this.testB = testB; } }
public class TestB { private TestC testC; public TestB(TestC testC) { this.testC = testC; } public void b(){ testC.c(); } public TestC getTestC() { return testC; } public void setTestC(TestC testC) { this.testC = testC; }}
public class TestC { private TestA testA; public TestC(TestA testA) { this.testA = testA; } public void c() { testA.a(); } public TestA getTestA() { return testA; } public void setTestA(TestA testA) { this.testA = testA; }}
结构器循环依赖
结构器循环依赖示意通过结构器注入造成的循环依赖,须要留神的是,这种状况是无奈解决的,会抛出BeanCurrentlyInCreationException
异样。
Spring容器会将每一个正在创立的Bean标识符放在一个“以后创立Bean池”中,Bean标识符在创立过程中将始终放弃在这个池中。如果在创立bean的过程中发现自己曾经在“以后创立Bean池”时,就会抛出上述异样示意循环依赖。当bean创立实现后则会从“以后创立Bean池”移除。
- 创立配置文件
<bean id="a" class="cn.jack.TestA"> <constructor-arg index="0" ref="b"/></bean><bean id="b" class="cn.jack.TestB"> <constructor-arg index="0" ref="c"/></bean><bean id="c" class="cn.jack.TestC"> <constructor-arg index="0" ref="a"/></bean>
- 创立测试用例
public class Test { public static void main(String[] args) { new ClassPathXmlApplicationContext("spring-config.xml"); }}
- 运行后果
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-config.xml]: Cannot resolve reference to bean 'c' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'c' defined in class path resource [spring-config.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
如果理解了方才形容的状况,咱们很容易就能够想到是在创立TestC对象的时候须要筹备其结构参数TestA,这时候Spring容器要去创立TestA,但发现该bean标识符曾经在“以后创立Bean池”中了,所以就抛出上述异样。
setter循环依赖
对于setter注入造成的依赖是通过Spring容器提前裸露刚实现结构器注入但还未实现其余步骤(比方setter注入)的bean来实现的,而且只能解决单例作用域下的bean循环依赖。通过提前裸露一个单例工厂办法,从而使其余bean能够援用到该bean。
依据咱们的代码案例流程如下:
- Spring容器创立单例TestA,随后调用无参结构器创立bean,并暴露出ObjectFactory,用于返回一个提前裸露创立中的bean,并将 testA 标识符放到 “以后创立bean池”中,而后进行setter注入 TestB
- Spring容器创立单例TestB,随后调用无参结构器创立bean,并暴露出ObjectFactory,用于返回一个提前裸露创立中的bean,并将 testB 标识符放到 “以后创立bean池”中,而后进行setter注入 TestC
- Spring容器创立单例TestC,随后调用无参结构器创立bean,并暴露出ObjectFactory,用于返回一个提前裸露创立中的bean,并将 testC 标识符放到 “以后创立bean池”中,而后进行setter注入 TestA ,因为之前曾经提前裸露了 ObjectFactory,从而应用它返回提前裸露的一个创立中的bean。其余同理。
prototype范畴的依赖解决
scope="prototype"意思是每次申请都会创立一个实例对象。
两者的区别是:有状态的bean都应用Prototype作用域,无状态的个别都应用singleton单例作用域。
对于“prototype”作用域Bean,Spring容器无奈实现依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因而无奈提前裸露一个创立中的Bean。所以还是会抛出上述异样。
流程解析
这里就拿TestA 依赖 TestB,TestB 依赖 TestA 举例。
在 TestA 和 TestB 循环依赖的场景中:
TestB populatedBean
查找依赖项 TestA 的时候,从一级缓存中尽管未获取到 TestA,然而发现 TestA 在创立中。
此时,从三级缓存中获取 A 的 singletonFactory
调用工厂办法,创立 getEarlyBeanReference
TestA 的晚期援用并返回。
二级缓存是否解决循环依赖
咱们晓得在实例化过程中,将处于半成品的对象地址全副放在缓存中,提前裸露对象,在后续的过程中,再次对提前裸露的对象进行赋值,而后将赋值实现的对象,也就是成品对象放在一级缓存中,删除二级和三级缓存。
如果不要二级缓存的话,一级缓存会存在半成品和成品的对象,获取的时候,可能会获取到半成品的对象,无奈应用。
如果不要三级缓存的话,未应用AOP的状况下,只须要一级和二级缓存也是能够解决Spring循环依赖;然而如果应用了AOP进行加强性能的话,必须应用三级缓存,因为在获取三级缓存过程中,会用代理对象替换非代理对象,如果没有三级缓存,那么就无奈失去代理对象。
三级缓存是为了解决AOP代理过程中产生的循环依赖问题。
咱们在代码上减少aop相干切面操作后,变动就在initializeBean办法中产生,调用applyBeanPostProcessorsBeforeInitialization办法。
在getBeanPostProcessors中中有一个处理器为: 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 的代理对象。必定是 false 。 所以不会再生成一次 TestA 的代理对象。
总结
TestA和TestB都存在动静代理状况下的流程图: