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池”移除。

  1. 创立配置文件
<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>
  1. 创立测试用例
public class Test {    public static void main(String[] args) {         new ClassPathXmlApplicationContext("spring-config.xml");    }}
  1. 运行后果
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都存在动静代理状况下的流程图: