Spring框架是怎么解决Bean之间的循环依赖的 (转)

问题:面试

  循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终造成闭环。好比A依赖于B,B依赖于C,C又依赖于A。以下图:spring

 

如何理解“依赖”呢,在Spring中有:缓存

  • 构造器循环依赖
  • field属性注入循环依赖

直接上代码:app

构造器循环依赖函数

@Service
public class A {  
    public A(B b) {  }
}

@Service
public class B {  
    public B(C c) {  
    }
}

@Service
public class C {  
    public C(A a) {  }
}

结果:项目启动失败,发现了一个cycleui

 

 2.field属性注入循环依赖this

@Service
public class A1 {  
    @Autowired  
    private B1 b1;
}

@Service
public class B1 {  
    @Autowired  
    public C1 c1;
}

@Service
public class C1 {  
    @Autowired  public A1 a1;
}

结果:项目启动成功spa

 

3.field属性注入循环依赖(prototype).net

@Service
@Scope("prototype")
public class A1 {  
    @Autowired  
    private B1 b1;
}

@Service
@Scope("prototype")
public class B1 {  
    @Autowired  
    public C1 c1;
}

@Service
@Scope("prototype")
public class C1 {  
    @Autowired  public A1 a1;
}

结果:项目启动失败,发现了一个cycle。prototype

 
 

现象总结:一样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。由于@Service默认是单例的,因此单例的属性注入是能够成功的。

 

Spring如何解决循环依赖

spring中循环依赖有三种状况:

  一、构造器注入造成的循环依赖。也就是beanB须要在beanA的构造函数中完成初始化,beanA也须要在beanB的构造函数中完成温馨化,这种状况的结果就是两个bean都不能完成初始化,循环依赖难以解决。

  二、setter注入构成的循环依赖。beanA须要在beanB的setter方法中完成初始化,beanB也须要在beanA的setter方法中完成初始化,spring设计的机制主要就是解决这种循环依赖,也是今天下文讨论的重点。

  三、prototype做用域bean的循环依赖。这种循环依赖一样没法解决,由于spring不会缓存‘prototype’做用域的bean,而spring中循环依赖的解决正是经过缓存来实现的。

下面主要说明第二种状况中循环依赖的解决方案

  步骤一:beanA进行初始化,而且将本身进行初始化的状态记录下来,并提早向外暴露一个单例工程方法,从而使其余bean能引用到该bean(可能读完这一句,您仍然心存疑惑,不要紧,继续往下读)

  步骤二:beanA中有beanB的依赖,因而开始初始化beanB。

  步骤三:初始化beanB的过程当中又发现beanB依赖了beanA,因而又进行beanA的初始化,这时发现beanA已经在进行初始化了,程序发现了存在的循环依赖,而后经过步骤一中暴露的单例工程方法拿到beanA的引用(注意,此时的beanA只是完成了构造函数的注入但为完成其余步骤),从而beanB拿到beanA的引用,完成注入,完成了初始化,如此beanB的引用也就能够被beanA拿到,从而beanA也就完成了初始化。

  spring进行bean的加载的时候,首先进行bean的初始化(调用构造函数),而后进行属性填充。在这两步中间,spring对bean进行了一次状态的记录,也就是说spring会把指向只完成了构造函数初始化的bean的引用经过一个变量记录下来,明白这一点对以后的源码理解相当重要。

源码角度观看循环依赖的解决步骤

步骤一中首先进行beanA的建立

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
    try {
        return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
        // Explicitly remove instance from singleton cache: It might have been put there
        // eagerly by the creation process, to allow for circular reference resolution.
        // Also remove any beans that received a temporary reference to the bean.
        destroySingleton(beanName);
        throw ex;
    }
});

进入getSingleton中,spirng会记录当前beanA正在建立中

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

而且将注册一个工厂方法来解决循环依赖

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isDebugEnabled()) {
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
     这里:    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

主要就是addSingletonFactory,这句就完成了工厂方法的注册,这个方法能够返回一个只完成了构造函数初始化的beanA,也许你们想知道他是如何返回的,咱们进入getEarlyBeanReference方法

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

  能够看到这个方法中,除了对后处理器的调用,没有进行任何动做,而是直接返回了咱们参数传入的bean,那么这个bean是哪来的呢?其实在这以前,spring先调用了beanA的构造函数,并拿到了只完成了构造函数初始化的一个实例,并把他记录了下来。

if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        final Object bean = instanceWrapper.getWrappedInstance();

  这就是这个bean的由来。而后当咱们进行到步骤三的时候,就会检查是否容许循环依赖(即便是Singleton类型的bean也能够经过参数设置,禁止循环依赖),若是容许的话,就会经过这个工厂方法拿到beanA的引用。从而完成beanA和beanB的加载。

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;
    }

 

也能够看这篇 : Spring如何解决循环依赖的问题

 

  在关于Spring的面试中,咱们常常会被问到一个问题,就是Spring是如何解决循环依赖的问题的。这个问题算是关于Spring的一个高频面试题,由于若是不刻意研读,相信即便读过源码,面试者也不必定可以一会儿思考出个中奥秘。本文主要针对这个问题,从源码的角度对其实现原理进行讲解。

1. 过程演示

        关于Spring bean的建立,其本质上仍是一个对象的建立,既然是对象,读者朋友必定要明白一点就是,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化。在Spring中,对象的实例化是经过反射实现的,而对象的属性则是在对象实例化以后经过必定的方式设置的。这个过程能够按照以下方式进行理解:

        理解这一个点以后,对于循环依赖的理解就已经帮助一大步了,咱们这里以两个类A和B为例进行讲解,以下是A和B的声明:

@Component
public class A {

  private B b;

  public void setB(B b) {
    this.b = b;
  }
}
@Component
public class B {

  private A a;

  public void setA(A a) {
    this.a = a;
  }
}

        能够看到,这里A和B中各自都以对方为本身的全局属性。这里首先须要说明的一点是,Spring实例化bean是经过ApplicationContext.getBean()方法来进行的。若是要获取的对象依赖了另外一个对象,那么其首先会建立当前对象,而后经过递归的调用ApplicationContext.getBean()方法来获取所依赖的对象,最后将获取到的对象注入到当前对象中。这里咱们以上面的首先初始化A对象实例为例进行讲解。首先Spring尝试经过ApplicationContext.getBean()方法获取A对象的实例,因为Spring容器中尚未A对象实例,于是其会建立一个A对象,而后发现其依赖了B对象,于是会尝试递归的经过ApplicationContext.getBean()方法获取B对象的实例,可是Spring容器中此时也没有B对象的实例,于是其仍是会先建立一个B对象的实例。读者须要注意这个时间点,此时A对象和B对象都已经建立了,而且保存在Spring容器中了,只不过A对象的属性b和B对象的属性a都尚未设置进去。在前面Spring建立B对象以后,Spring发现B对象依赖了属性A,于是此时仍是会尝试递归的调用ApplicationContext.getBean()方法获取A对象的实例,由于Spring中已经有一个A对象的实例,虽然只是半成品(其属性b还未初始化),但其也仍是目标bean,于是会将该A对象的实例返回。此时,B对象的属性a就设置进去了,而后仍是ApplicationContext.getBean()方法递归的返回,也就是将B对象的实例返回,此时就会将该实例设置到A对象的属性b中。这个时候,注意A对象的属性b和B对象的属性a都已经设置了目标对象的实例了。读者朋友可能会比较疑惑的是,前面在为对象B设置属性a的时候,这个A类型属性仍是个半成品。可是须要注意的是,这个A是一个引用,其本质上仍是最开始就实例化的A对象。而在上面这个递归过程的最后,Spring将获取到的B对象实例设置到了A对象的属性b中了,这里的A对象其实和前面设置到实例B中的半成品A对象是同一个对象,其引用地址是同一个,这里为A对象的b属性设置了值,其实也就是为那个半成品的a属性设置了值。下面咱们经过一个流程图来对这个过程进行讲解:

        图中getBean()表示调用Spring的ApplicationContext.getBean()方法,而该方法中的参数,则表示咱们要尝试获取的目标对象。图中的黑色箭头表示一开始的方法调用走向,走到最后,返回了Spring中缓存的A对象以后,表示递归调用返回了,此时使用绿色的箭头表示。从图中咱们能够很清楚的看到,B对象的a属性是在第三步中注入的半成品A对象,而A对象的b属性是在第二步中注入的成品B对象,此时半成品的A对象也就变成了成品的A对象,由于其属性已经设置完成了。

2. 源码讲解

        对于Spring处理循环依赖问题的方式,咱们这里经过上面的流程图其实很容易就能够理解,须要注意的一个点就是,Spring是如何标记开始生成的A对象是一个半成品,而且是如何保存A对象的。这里的标记工做Spring是使用ApplicationContext的属性Set<String> singletonsCurrentlyInCreation来保存的,而半成品的A对象则是经过Map<String, ObjectFactory<?>> singletonFactories来保存的,这里的ObjectFactory是一个工厂对象,可经过调用其getObject()方法来获取目标对象。在AbstractBeanFactory.doGetBean()方法中获取对象的方法以下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  // 尝试经过bean名称获取目标bean对象,好比这里的A对象
  Object sharedInstance = getSingleton(beanName);
  
  // 咱们这里的目标对象都是单例的
  if (mbd.isSingleton()) {
    // 这里就尝试建立目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
    // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来建立
    // 目标对象
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // 尝试建立目标对象
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throw ex;
      }
    });
  }
  return (T) bean;
}

        这里的doGetBean()方法是很是关键的一个方法(中间省略了其余代码),上面也主要有两个步骤,第一个步骤的getSingleton()方法的做用是尝试从缓存中获取目标对象,若是没有获取到,则尝试获取半成品的目标对象;若是第一个步骤没有获取到目标对象的实例,那么就进入第二个步骤,第二个步骤的getSingleton()方法的做用是尝试建立目标对象,而且为该对象注入其所依赖的属性。

        这里其实就是主干逻辑,咱们前面图中已经标明,在整个过程当中会调用三次doGetBean()方法,第一次调用的时候会尝试获取A对象实例,此时走的是第一个getSingleton()方法,因为没有已经建立的A对象的成品或半成品,于是这里获得的是null,而后就会调用第二个getSingleton()方法,建立A对象的实例,而后递归的调用doGetBean()方法,尝试获取B对象的实例以注入到A对象中,此时因为Spring容器中也没有B对象的成品或半成品,于是仍是会走到第二个getSingleton()方法,在该方法中建立B对象的实例,建立完成以后,尝试获取其所依赖的A的实例做为其属性,于是仍是会递归的调用doGetBean()方法,此时须要注意的是,在前面因为已经有了一个半成品的A对象的实例,于是这个时候,再尝试获取A对象的实例的时候,会走第一个getSingleton()方法,在该方法中会获得一个半成品的A对象的实例。而后将该实例返回,而且将其注入到B对象的属性a中,此时B对象实例化完成。而后将实例化完成的B对象递归的返回,此时就会将该实例注入到A对象中,这样就获得了一个成品的A对象。咱们这里能够阅读上面的第一个getSingleton()方法:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 尝试从缓存中获取成品的目标对象,若是存在,则直接返回
  Object singletonObject = this.singletonObjects.get(beanName);
  // 若是缓存中不存在目标对象,则判断当前对象是否已经处于建立过程当中,在前面的讲解中,第一次尝试获取A对象
  // 的实例以后,就会将A对象标记为正在建立中,于是最后再尝试获取A对象的时候,这里的if判断就会为true
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的
        // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,不管是不是半成品
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 获取目标对象的实例
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

        这里咱们会存在一个问题就是A的半成品实例是如何实例化的,而后是如何将其封装为一个ObjectFactory类型的对象,而且将其放到上面的singletonFactories属性中的。这主要是在前面的第二个getSingleton()方法中,其最终会经过其传入的第二个参数,从而调用createBean()方法,该方法的最终调用是委托给了另外一个doCreateBean()方法进行的,这里面有以下一段代码:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  throws BeanCreationException {

  // 实例化当前尝试获取的bean对象,好比A对象和B对象都是在这里实例化的
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  
  // 判断Spring是否配置了支持提早暴露目标bean,也就是是否支持提早暴露半成品的bean
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
    && isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // 若是支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories
    // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是
    // 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是
    // 目标bean直接返回
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  try {
    // 在初始化实例以后,这里就是判断当前bean是否依赖了其余的bean,若是依赖了,
    // 就会递归的调用getBean()方法尝试获取目标bean
    populateBean(beanName, mbd, instanceWrapper);
  } catch (Throwable ex) {
    // 省略...
  }
  
  return exposedObject;
}

        到这里,Spring整个解决循环依赖问题的实现思路已经比较清楚了。对于总体过程,读者朋友只要理解两点:

  • Spring是经过递归的方式获取目标bean及其所依赖的bean的;
  • Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,而后为其注入属性。

        结合这两点,也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的全部bean,直到某个bean没有依赖其余bean,此时就会将该实例返回,而后反递归的将获取到的bean设置为各个上层bean的属性的。

3. 小结

        本文首先经过图文的方式对Spring是如何解决循环依赖的问题进行了讲解,而后从源码的角度详细讲解了Spring是如何实现各个bean的装配工做的。

 

 

出处:

  https://cloud.tencent.com/developer/article/1455953

  https://www.jianshu.com/p/8bb67ca11831

相关文章
相关标签/搜索