1. 什么是循环依托?

浅显来讲,循环依托指的是一个实例或多个实例存在相互依托的关系(类之间循环嵌套引用)。

举个比如

public class AService {
    private BService bService;
}
public class BService {
    private AService aService;
}

上述比如中 AService 依托了 BServiceBService 也依托了 AService,这便是两个目标之间的相互依托。当然循环依托还包含 自身依托、多个实例之间相互依托

浅谈 Spring 如何解决 Bean 的循环依赖问题

正常运转上面的代码调用 AService 目标并不会呈现问题,也便是说一般目标就算呈现循环依托也不会存在问题,由于目标之间存在依托关系是很常见的,那么为什么被 Spring 容器管理后的目标会呈现循环依托问题呢?


2. Spring Bean 的循环依托问题

被 Spring 容器管理的目标叫做 Bean,为什么 Bean 会存在循环依托问题呢?

想要了解 Bean 的循环依托问题,首先需求了解 Bean 是怎么创立的。

2.1 Bean 的创立过程

为了能更好的展现呈现循环依托问题的环节,所以这儿的 Bean 创立过程做了简化:

  1. 在创立 Bean 之前,Spring 会经过扫描获取 BeanDefinition。
  2. BeanDefinition安排妥当后会读取 BeanDefinition 中所对应的 class 来加载类。
  3. 实例化阶段:依据结构函数来完结实例化 (未特点注入以及初始化的目标 这儿简称为 原始目标
  4. 特点注入阶段:对 Bean 的特点进行依托注入 (这儿便是发生循环依托问题的环节
  5. 假如 Bean 的某个办法有AOP操作,则需求依据原始目标生成署理目标
  6. 终究把署理目标放入单例池(一级缓存singletonObjects)中。

上面的过程首要是为了杰出循环依托问题,假如想了解 Bean 的完好生命周期能够看这一篇文章:浅谈 Spring Bean 的生命周期 – ()

两点阐明:

  • 上面的 Bean 创立过程是对于 单例(singleton) 效果域的 Bean。
  • Spring 的 AOP 署理便是作为 BeanPostProcessor 完结的,而 BeanPostProcessor 是发生在特点注入阶段后的,所以 AOP 是在 特点注入 后履行的。

2.2 为什么 Spring Bean 会发生循环依托问题?

经过上面的 Bean 创立过程可知:实例化 Bean 后会进行 特点注入(依托注入)

如上面的 AServiceBService 的依托关系,当 AService 创立时,会先对 AService 进行实例化生成一个原始目标,然后在进行特点注入时发现了需求 BService 对应的 Bean,此刻就会去为 BService 进行创立,在 BService 实例化后生成一个原始目标后进行特点注入,此刻会发现也需求 AService 对应的 Bean。

浅谈 Spring 如何解决 Bean 的循环依赖问题

这样就会构成 AServiceBService 的 Bean 都无法创立,就会发生 循环依托 问题。


2.3 三大循环依托问题场景

Spring 并不能处理所有 Bean 的循环依托问题,接下来经过比如来看看哪些场景下的循环依托问题是不能被处理的。

AService

/**
 * @author 单程车票
 */
public class AService {
    private BService bService;
    public AService() {
    }
    public AService(BService bService) {
        this.bService = bService;
    }
    public BService getbService() {
        return bService;
    }
    public void setbService(BService bService) {
        this.bService = bService;
    }
}

BService

/**
 * @author 单程车票
 */
public class BService {
    private AService aService;
    public BService() {
    }
    public BService(AService aService) {
        this.aService = aService;
    }
    public AService getaService() {
        return aService;
    }
    public void setaService(AService aService) {
        this.aService = aService;
    }
}

测验类

/**
 * 测验类
 * @author 单程车票
 */
public class Application {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml");
        AService aService = (AService) applicationContext.getBean("aService");
        System.out.println("履行成功,获取AService目标为:" + aService);
    }
}

单例效果域下的 Setter办法注入 / field特点注入 呈现的循环依托

application-context.xml 配置文件

运用 property 标签也便是 Setter办法 进行特点注入。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="aService" class="com.xqsr.springtest.service.AService">
        <property name="bService" ref="bService"/>
    </bean>
    <bean id="bService" class="com.xqsr.springtest.service.BService">
        <property name="aService" ref="aService"/>
    </bean>
</beans>

运转成果

浅谈 Spring 如何解决 Bean 的循环依赖问题

能够看到 Setter办法注入方式 在 Spring 中是不会发生循环依托问题的,这首要是靠 三级缓存 机制(下文会详细阐明)。

单例效果域下的 结构器注入 呈现的循环依托

application-context.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="aService" class="com.xqsr.springtest.service.AService">
        <constructor-arg name="bService" ref="bService"/>
    </bean>
    <bean id="bService" class="com.xqsr.springtest.service.BService">
        <constructor-arg name="aService" ref="aService"/>
    </bean>
</beans>

运转成果

抛出 BeanCurrentlyInCreationException 反常,阐明 Spring 无法处理 结构器注入 呈现的循环依托问题。

浅谈 Spring 如何解决 Bean 的循环依赖问题

原因:由于 结构器注入 发生在 实例化阶段,而 Spring 处理循环依托问题依托的 三级缓存特点注入阶段,也便是说调用结构函数时还未能放入三级缓存中,所以无法处理 结构器注入 的循环依托问题。

原型 效果域下的特点注入呈现的循环依托问题

application-context.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="aService" class="com.xqsr.springtest.service.AService" scope="prototype">
        <property name="bService" ref="bService"/>
    </bean>
    <bean id="bService" class="com.xqsr.springtest.service.BService" scope="prototype">
        <property name="aService" ref="aService"/>
    </bean>
</beans>

运转成果

相同抛出 BeanCurrentlyInCreationException 反常,阐明 Spring 无法处理 原型效果域 呈现的循环依托问题。

浅谈 Spring 如何解决 Bean 的循环依赖问题

原因:由于 Spring 不会缓存 原型 效果域的 Bean,而 Spring 依托 缓存 来处理循环依托问题,所以 Spring 无法处理 原型 效果域的 Bean。


3. Spring 怎么处理循环依托问题?

经过上文的内容能了解到 Spring 为什么会发生循环依托问题 以及 Spring 能处理什么场景下的循环依托问题

上文中也有提到过 Spring 是靠 三级缓存 来处理循环依托问题的,接下来了解一下 什么是三级缓存 以及 处理循环依托问题的详细流程

3.1 三级缓存是什么?

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

三级缓存分为:

  • 一级缓存(singletonObjects):缓存的是已经实例化、特点注入、初始化后的 Bean 目标。
  • 二级缓存(earlySingletonObjects):缓存的是实例化后,但未特点注入、初始化的 Bean目标(用于提早露出 Bean)。
  • 三级缓存(singletonFactories):缓存的是一个 ObjectFactory,首要效果是生成原始目标进行 AOP 操作后的署理目标(这一级缓存首要用于处理 AOP 问题,后续文章中解说)。

3.2 为什么缓存能够处理循环依托问题?

(注意这儿只是为了阐明缓存能够处理循环依托问题,可是 Spring 实际上并不是这样做的)

上文中能够看到 AServiceBService 的循环依托问题是由于 AService的创立 需求 BService的注入BService的注入 需求 BService的创立BService的创立 需求 AService的注入AService的注入 需求 AService的创立,然后构成的环形调用。

想要打破这一环形,只需求增加一个 缓存 来寄存 原始目标 即可。

在创立 AService 时,实例化后将 原始目标 寄存到缓存中(提早露出),然后依托注入时发现需求 BService,便会去创立 BService,实例化后相同将 原始目标 寄存到缓存中,然后依托注入时发现需求 AService 便会从缓存中取出并注入,这样 BService 就完结了创立,随后 AService 也就能完结特点注入,终究也完结创立。这样就打破了环形调用,避免循环依托问题。

浅谈 Spring 如何解决 Bean 的循环依赖问题


3.3 为什么还需求第三级缓存?

经过上面的分析能够发现只需求一个寄存 原始目标 的缓存就能够处理循环依托问题,也便是说只要二级缓存(earlySingletonObjects)就够了,那么为什么 Spring 还设置了三级缓存(singletonFactories)呢?

其实 第三级缓存(singletonFactories 是为了处理 Spring 的 AOP的。

如上面的比如假如 AService 中办法没有运用 AOP 操作,会发现 BService 注入的 原始目标 与终究 AService 完结创立后的终究目标是同一个目标

假如 AService 办法中有 AOP 操作,Bean 的创立会如下图:

浅谈 Spring 如何解决 Bean 的循环依赖问题

所以假如 AService 办法中有 AOP 操作时,当 AService 的原始目标赋值(注入)给 BServiceAService 会进行 AOP 操作发生一个 署理目标,这个署理目标终究会被放入单例池(一级缓存)中,也便是说此刻 BService 中注入的目标是原始目标,而 AService 终究创立的完结后是署理目标,这样就会导致 BService 依托的 AService 和 终究的 AService 不是同一个目标

呈现这个问题首要是上文提到过的 AOP 是经过 BeanPostProcessor 完结的,而 BeanPostProcessor 是在 特点注入阶段后 才履行的,所以会导致注入的目标有或许和终究的目标不一致


3.4 Spring 是怎么经过第三级缓存来避免 AOP 问题的?

三级缓存中寄存的是 ObjectFactory 目标,那 ObjectFactory 是什么呢?

ObjectFactory 是什么?

深化源码会发现 Spring 在 doCreateBean() 办法中的 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)) 参加缓存。

浅谈 Spring 如何解决 Bean 的循环依赖问题

深化 addSingletonFactory 办法:能够看到办法中的第二个参数便是 ObjectFactory 类型,并且将其添加进 三级缓存(singletonFactories 中。

浅谈 Spring 如何解决 Bean 的循环依赖问题

这儿放一下 ObjectFactory 类:

浅谈 Spring 如何解决 Bean 的循环依赖问题

也便是说 Spring 在参加缓存时,会将 实例化后生成的原始目标 经过 lambda 表达式调用 getObject() 办法,getObject() 办法里调用 getEarlyBeanReference() 办法 来封装成 ObjectFactory 目标。

getEarlyBeanReference() 办法的效果

进入 getEarlyBeanReference() 中,会发现调用了 SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference() 办法。

浅谈 Spring 如何解决 Bean 的循环依赖问题

找到 SmartInstantiationAwareBeanPostProcessor 的完结类 AbstractAutoProxyCreator 完结的 getEarlyBeanReference() 办法就能够看到其效果了。

浅谈 Spring 如何解决 Bean 的循环依赖问题

  • earlyProxyReferences 存储的是 (beanName, bean) 键值对,这儿的 bean 指的是原始目标(刚实例化后的目标)。
  • wrapIfNecessary() 办法用于履行 AOP 操作,生成一个署理目标(也便是说假如有 AOP 操作终究回来的是署理目标,不然回来的还是原始目标)。

Spring 真实意义上地创立 Bean 的流程

先放详细流程图:

浅谈 Spring 如何解决 Bean 的循环依赖问题

关键阐明:

  1. 并不是马上就履行 ObjectFactorygetEarlyBeanReference() 办法(有循环依托时才履行)。

    • 实例化后的 Bean 会生成原始目标,然后经过 lambda 表达式封装为 ObjectFactory 目标,并且经过 addSingletonFactory() 办法将其放入 三级缓存(singletonFactories)中。
    • 可是这儿执不履行 lambda 表达式中的 getEarlyBeanReference() 办法是看程序有没有调用 singletonFactories.get(beanName),只有调用了该办法(其实也便是看是否存在循环依托需求提早获得该 Bean),才会触发履行 getEarlyBeanReference() 办法。
    • getEarlyBeanReference() 办法会依据 Bean 中是否有 AOP 操作来决定回来的是 原始目标 还是 署理目标,并且会将其上移到二级缓存中(也便是提早露出出来让别的 Bean 运用)。
  2. 假如 Bean 中有 AOP 操作,而 AOP 操作又是在特点注入之后履行的,那么之前的 getEarlyBeanReference() 办法中履行的 AOP 操作会不会重复

    • 答案是不会,还记得 getEarlyBeanReference() 办法中的 earlyProxyReferences 吗,这个便是用来记录当时 Bean 是否已经履行 AOP 操作。
    • 当特点注入后需求履行 AOP 操作时,会先判别当时的 Bean 是否在 earlyProxyReferences 中,假如在则阐明已经提早履行了 AOP 了,不必再履行了,不然就履行当时 AOP 操作。
  3. 二级缓存中的目标什么时候会上移到一级缓存?

    • 二级缓存是为了提早露出 Bean 来处理循环依托问题,此刻的 Bean 或许还没有进行特点注入,只有等完结了特点注入、初始化后的 Bean 才会上移到一级缓存(单例池)中。
  4. 为什么能够处理 AOP 的问题?

    • 三级缓存经过使用 ObjectFactorygetEarlyBeanReference() 做到了提早履行 AOP 操作然后生成署理目标。
    • 这样在上移到二级缓存时,能够做到假如 Bean 中有 AOP 操作,那么提早露出的目标会是 AOP 操作后回来的署理目标;假如没有 AOP 操作,那么提早露出的目标会是原始目标。
    • 这样就能做到呈现循环依托问题时,注入依托的目标和终究生成的目标是同一个目标。(相当于 AOP 提早在特点注入前完结,这样就不会导致后面生成的署理目标与特点注入时的目标的不一致)

所以 Spring 使用 三级缓存 奇妙地将呈现 循环依托 时的 AOP 操作 提早到了 特点注入 之前,避免了目标不一致问题。


4. 整理 Spring 处理 Bean 的循环依托的整个流程

还是以 AServiceBService 的循环依托为例,完好地看看 Spring 是怎么处理 Bean 的循环依托问题。

源码分析整个流程

由于前面的内容过于繁琐,这儿就以文字概括,只重视几个首要的办法:

AbstractApplicationContextrefresh() 办法出发,进入 finishBeanFactoryInitialization() 办法再进入 preInstantiateSingletons() 办法再进入 getBean() 办法再进入 doGetBean() 办法。

看看 doGetBean() 办法:

浅谈 Spring 如何解决 Bean 的循环依赖问题

其中的榜首个 getSingleton(beanName) 是判别 三级缓存 中是否有创立好的 Bean 目标,看看源码:

浅谈 Spring 如何解决 Bean 的循环依赖问题

能够看到这儿分别去每一级的缓存中取数据,依次从榜首级开端取数据,假如取得到则直接回来,取不到则往下一级查找。

能够看到在第三级缓存中调用了 singletonFactories.get(beanName) 依照上文所说的会触发履行有 AOP 操作回来署理目标,没有回来原始目标,并且在这儿会判别取出的数据是否存在,存在则上移到二级缓存中并删除三级缓存的数据。

假如都没有的话就会履行第二个 getSingleton() 也便是去履行 createBean() 创立一个 Bean 目标出来。

会履行 createBean() 办法中的 doCreateBean() 办法,看看源码:

浅谈 Spring 如何解决 Bean 的循环依赖问题

到这儿应该就一望而知了。

整理整个流程

  1. 首先会获取 AService 对应的 Bean 目标。
  2. 先是调用 doGetBean() 中的榜首个 getSingleton(beanName) 判别是否有该 Bean 的实例,有就直接回来了。(明显这儿没有)
  3. 然后调用 doGetBean() 中的第二个 getSingleton() 办法来履行 doCreateBean() 办法。
  4. 先进行实例化操作(也便是使用结构函数实例化),此刻实例化后生成的是原始目标。
  5. 将原始目标经过 lambda表达式 进行封装成 ObjectFactory 目标,经过 addSingletonFactory 参加三级缓存中。
  6. 然后再进行特点注入,此刻发现需求注入 BService 的 Bean,会经过 doGetBean() 去获取 BService 对应的 Bean。
  7. 相同调用 doGetBean() 中的榜首个 getSingleton(beanName) 判别是否有该 Bean 的实例,明显这儿也是不会有 BService 的 Bean 的。
  8. 然后只能调用 doGetBean() 中的第二个 getSingleton() 办法来履行 doCreateBean() 办法来创立一个 BService 的 Bean。
  9. 相同地先进行实例化操作,生成原始目标后封装成 ObjectFactory 目标放入三级缓存中。
  10. 然后进行特点注入,此刻发现需求注入 AService 的 Bean,此刻调用调用 doGetBean() 中的榜首个 getSingleton(beanName) 查找是否有 AService 的 Bean。此刻会触发三级缓存,也便是调用 singletonFactories.get(beanName)
  11. 由于三级缓存中有 AService 的原始目标封装的 ObjectFactory 目标,所以能够获取到的署理目标或原始目标,并且上移到二级缓存中,提早露出给 BService 调用。
  12. 所以 BService 能够完结特点注入,然后进行初始化后,将 Bean 放入一级缓存,这样 AService 也能够完结创立。

以上便是 Spring 处理 Bean 的循环依托问题的整个流程了。