Spring循环依赖产生与解决

Spring的解决循环依赖是有前置条件的,要解决循环依赖我们首先要了解Spring Bean对象的创建过程和依赖注入的方式。依赖注入方式,我之前的博客有所分享,大家可以在看本篇文章之前进行一下小小的回顾

循环依赖产生情景

探讨如何解决循环依赖之前,更应该思考清楚什么情况下会发生这种问题?

1、模拟Prototype Bean的循环依赖

static class BeanA { // 1. 属性循环依赖 BeanB beanB = new BeanB(); // 2. 构造器循环依赖 public BeanA(BeanB beanB) { this.beanB = beanB; } } static class BeanB { // 1. 属性循环依赖 BeanA beanA = new BeanA(); // 2. 构造器循环依赖 public BeanB(BeanA beanA) { this.beanA = beanA; } } public static void main(String[] args) { // 1. 属性循环依赖 BeanA beanA = new BeanA(); // StackOverflowError // 2. 构造器循环依赖 new BeanA(new BeanB(new BeanA(new BeanB( /*....*/ )))); }

Prototype bean,构造器注入Bean时,强调注入的是成品Bean,无法解决循环依赖问题。

Prototype bean,属性上注入Bean时,由于原型模式是单个bean可以被多次创建的,一旦发生循环依赖,就会产生上面这种不断在创建新的BeanA与BeanB导致的栈溢出。源码中的解决方式:记录并检测,如下:

protected  T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // ... // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // ... else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } // ... } protected boolean isPrototypeCurrentlyInCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set) curVal).contains(beanName)))); } protected void beforePrototypeCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal == null) { this.prototypesCurrentlyInCreation.set(beanName); } else if (curVal instanceof String) { Set beanNameSet = new HashSet<>(2); beanNameSet.add((String) curVal); beanNameSet.add(beanName); this.prototypesCurrentlyInCreation.set(beanNameSet); } else { Set beanNameSet = (Set) curVal; beanNameSet.add(beanName); } }

获取Bean前先判断是否当前Bean是否为Prototype并且在创建中。如果不是,且当前Bean为Prototype,那么会记录当前Bean正在创建中(通过beforePrototypeCreation方法)。

以模拟循环依赖代码为例:

  • 创建BeanA
  • populateBean(BeanA)
  • 创建BeanB
  • populateBean(BeanB)
  • 创建BeanA,isPrototypeCurrentlyInCreation发现BeanA正常创建中,抛出BeanCurrentlyInCreationException

2、模拟Singleton Bean的循环依赖

static class BeanA { BeanB beanB; public BeanA(BeanB beanB) { this.beanB = beanB; } } static class BeanB { BeanA beanA; public BeanB(BeanA beanA) { this.beanA = beanA; } } public static void main(String[] args) { // 1. 属性循环依赖 BeanA beanA = new BeanA(); BeanB beanB = new BeanB(); beanA.beanB = beanB; beanB.beanA = beanA; // 2. 构造器循环依赖 new BeanA(new BeanB(new BeanA(new BeanB( /*....*/ )))); }

Singleton bean,构造器注入Bean时,强调注入的是成品Bean,无法解决循环依赖问题。

Singleton bean,属性上注入Bean时,允许注入提前暴露的半成品的Bean,即没有填充属性&初始化的Bean,只要引用关系能关联上,属性填充与初始化随着程序的执行自然就会处理完成,可以解决循环依赖。

为什么构造器中不能先传递一个半成品Bean,然后赋值给成员Bean属性呢?而一定要传递一个成品Bean?很好理解,spring并不能确定用户在构造器中的操作仅为赋值给Bean属性。且很多时候,我们需要通过bean的一些注入的Bean属性来执行一些业务操作的。如果为空,那么就可能会发生空指针异常。而且这样也不是特别合理。若用户选择属性注入的方式,用户不会涉及这些操作,那么自然就不需要考虑这些问题。

3、总结成一句话来说:spring仅能解决单例Bean下发生在属性上注入Bean产生的循环依赖。

Spring如何解决循环依赖

通过三级缓存解决循环依赖,分别是:

1、singletonObjects: 一级缓存,存储成品Bean

2、earlySingletonObjects: 二级缓存,存储半成品Bean

3、singletonFactories: 三级缓存,存储生成半成品Bean的ObjectFactory的lambda

还是以BeanA注入BeanB,BeanB注入BeanA为例,描述一下大致的执行流程:

  • 检查BeanA的缓存信息
  • 反射实例化BeanA
  • 注册暴露 BeanA的lambda(生成放入二级缓存中的半成品BeanA)到三级缓存:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
  • 填充属性BeanB:populateBean
  • 检查BeanB的缓存信息
  • 注册暴露 BeanB的lambda(生成放入二级缓存中的半成品BeanB)到三级缓存
  • 反射实例化BeanB
  • 填充属性BeanA:populateBean
  • 检查BeanA的缓存信息,从三级缓存中获取到了生成半成品BeanA的lambda,执行获取半成品BeanA并放入二级缓存,并在三级缓存中移除lambda
  • 将生成的BeanA的二级缓存对象赋值到BeanB的属性上
  • 将BeanB放入一级缓存,并在二、三级缓存中清理关于BeanB的记录
  • 初始化BeanB
  • 返回到第四步,将成品BeanB赋值到BeanA的属性上
  • 将BeanA放入一级缓存,并在二、三级缓存中清理关于BeanA的记录
  • 初始化BeanA

思考一下,是否能通过二级缓存来解决循环依赖?

首先spring对于三级缓存的应用,就是在生成需要提前暴露的半成品Bean,要么返回的是通过反射实例化后的对象,要么是被一组SmartInstantiationAwareBeanPostProcessor处理后的对象(比如生成代理Bean),然后在放到二级缓存中。那么我们在放入二级缓存时,不通过三级缓存获取这个过程,直接在方法中复现这个过程,在放入二级缓存中,效果想必也是相同的。

到此这篇关于Spring循环依赖产生与解决的文章就介绍到这了,更多相关Spring循环依赖内容请搜索0133技术站以前的文章或继续浏览下面的相关文章希望大家以后多多支持0133技术站!

以上就是Spring循环依赖产生与解决的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » Java