Spring是如何解决循坏依赖的?

本文深入探讨了Spring框架中如何处理循环依赖问题,特别是在单例模式下A类与B类互相依赖时的解决策略。介绍了Spring内部通过一级、二级、三级缓存机制确保正确注入最终形态的Bean。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是循环依赖?举一个例子,所谓的循环依赖就是现在有一个A类和一个B类,A里面属性注入了B,也就是依赖了B,B里面也依赖了A,那么这就是循环依赖了,而我们要探究的是spring是怎么在实例化A并且初始化的时候注入B,然后实例化B并且初始化的时候又是怎么能够注入到A这个过程。

我们先来看getBean这个方法,因为这个方法里面实现了一个Bean实例化到初始化最终变成一个完整的bean的过程

org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)

@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 

Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
   if (logger.isTraceEnabled()) {
      if (isSingletonCurrentlyInCreation(beanName)) {
         logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
               "' that is not fully initialized yet - a consequence of a circular reference");
      }
      else {
         logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
      }
   }
   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

 其中doGetBean里面一开始有一段这样的代码,逻辑也很简单,就是从getSingleton方法中尝试获取bean,如果获取到了就直接返回这个bean了

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

 在这里我们先对这段代码有一点印象,之后再来看这段代码

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory)

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation

继续回到doGetBean方法,该方法正常流程会走到

protected void beforeSingletonCreation(String beanName) {
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
}

 在getSingleton方法中首先会从单例对象池中判断是否已经有这个bean了,如果没有就会走下一步,而这个下一步里面会调用到父类的一个beforeSingletonCreation方法,在这个方法中会把当前的beanName放入到singletonsCurrentlyInCreation这个集合中,而这个集合里面存放的是当前正在创建的beanName,这个存放当前正在创建的beanName的集合对于解决循环依赖来说很重要,而为什么在这里才把这个bean放到这个集合中?我猜想是因为当spring实例化一个bean的时候走到这一步,确定了当前容器没有这个bean了,就会从这里开始标识我要开始创建bean了,就会把这个bean存入这个集合当中。在确定了这个bean需要创建的时候,就会调用到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

doGetBean就是包含了spring实例化初始化bean过程的实现方法了,这其中在bean实例化之后,属性注入之前,有一段很重要的代码

//判断这个bean是否符合循环依赖的条件
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

 在这一段代码中首先会判断这个bean是否符合循环依赖的条件,条件为这个bean必须是单例的,而且allowCircularReferences属性必须为true(这个属性可以通过外部api进行配置),以及这个bean是否是正在创建的,如果这些条件都符合的话,就会调用到addSingletonFactory方法

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory 

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
       //判断一级缓存中是否有当前的bean
      if (!this.singletonObjects.containsKey(beanName)) {
          //如果没有,则把创建这个bean的工厂放到二级缓存中
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}

这里说一下上面的bean工厂,这个bean工厂spring传入的是一段lambda表达式,具体调用这个bean工厂的getObject方法的时候,会调用到getEarlyBeanReference方法,这个方法中其实是能够得到一个bean的最终状态。好了,说了这么多,我们就需要把上面的东西都串起来,又以A和B为例子,假如上面getBean的流程是对A进行了,也就是getBean(A),那么此时当它走完上面的代码就会来到属性注入的逻辑了,也就是populateBean方法,往A里面注入B,当注入B的时候就会发现B依赖了A,那么如果不解决循坏依赖的问题就会产生一个死循坏了,当前的循坏如下面所示:

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)

而spring怎么解决这个死循坏的呢,就是通过上面的东西了,接下来重点看一下B注入A的时候调用getBean(A)的过程。重新来到getBean的代码,上面说过了B的getBean(A)会经过getSingleton的方法尝试去容器里面拿A

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->getSingleton(A)->三级缓存或者二级缓存中得到A->B得到A并注入->B走完剩下的生命周期->把B放入一级缓存->回到A的属性注入生命周期,此时得到B了并注入->A走完剩下的声明周期->把A放到一级缓存

 所以说在B注入A然后调用getBean(A)的时候,在getSingleton就能拿到A了,此时B的属性注入完成,直到B的生命周期结束,然后回到A的属性注入生命周期,最后A的生命周期结束,最终循坏依赖解决,流程如下:

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->getSingleton(A)->三级缓存或者二级缓存中得到A->B得到A并注入->B走完剩下的生命周期->把B放入一级缓存->回到A的属性注入生命周期,此时得到B了并注入->A走完剩下的声明周期->把A放到一级缓存

在这个过程中,可以发现,解决循坏依赖的关键点在于,记录了A是否是正在创建的,并且在A属性注入之前会提前暴露了A的工厂,在A注入B然后B去注入A调用getBean(A)的时候就能够拿到A之前提前暴露的工厂,从这个工厂中拿到A 

二级缓存的意义?

前面说了这个二级缓存就是存放一个bean的工厂的,这个bean工厂的作用就是生产这个bean,那么在A属性注入之前我们直接暴露A不就好了吗,反正此时A已经被实例化了,何必大费周章地去搞一个A的工厂呢?也就是说是否可以直接把实例化好的A放入到三级缓存中就好了,并不需要二级缓存?如果是普通的一个bean的话二级缓存确实是没必要的,但是我们需要考虑的一种场景就是代理

getBean(A)->实例化A->把实例A放到三级缓存中->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->直接从三级缓存中拿到A->B属性注入A完成并且走完剩余的生命周期->A属性注入B完成继续自己的生命周期->判断A是否需要代理->生成A的代理对象->把A的代理对象放到一级缓存

可以看到如果是直接把A放入到三级缓存,然后B从三级缓存中去拿到A并注入,当A完整地走完自己的生命周期的时候你会发现此时的A已经是变成一个代理对象了,而B依赖的那个A却还只是一个普通对象,而引入二级缓存就能够解决这个问题,因为二级缓存中放的是A的工厂,而既然是工厂,那么就能产生出A的代理对象了,也就是上面一直说的A的最终形态,因为在A还没走完自己的生命周期的时候,A这个对象是有可能变的,所以我们不能够直接暴露出这个A实例,而是需要暴露出A的工厂,从这个工厂才能拿到A的最终形态! 

getBean(A)->实例化A->把A的工厂放到二级缓存中->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->从二级缓存中拿到A的工厂,进而从工厂中拿到A的最终形态,比如A的代理对象->B属性注入A完成->B走完剩余的生命周期->把B放到一级缓存->A属性注入B完成继续自己的生命周期->判断A是否需要代理->生成A的代理对象->把A的代理对象放到一级缓存 

三级缓存的意义? 

既然上面说了二级缓存存的是bean的工厂,而且这个工厂这么牛逼能够产生bean的最终形态,那干嘛还用三级缓存?直接从工厂里面拿这个最终形态不就好了吗?我们看下spring是怎么做的

singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
   ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
   if (singletonFactory != null) {
      singletonObject = singletonFactory.getObject();
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
   }
}

spring每一次都是从一个三级缓存中拿bean,如果为空就从二级缓存中的bean工厂产生bean并且放到三级缓存中,这样做的目的就是减少性能的消耗,因为假如A依赖了100个对象,而这个100个对象又依赖了A,那么这100个对象每一次从工厂中拿一个最终形态的A都是一个极其复杂的过程,所以既然这个A也已经是个最终形态的A了,那么就能够直接缓存起来,下次需要用的需要直接从三级缓存拿就可以了 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值