乐趣区

关于java:Spring源码之Bean的加载三

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 都存在动静代理状况下的流程图:

退出移动版