Spring是如何解决循环依赖的

前言

记录下spring源码阅读过程中的一些心得,好记性不如烂笔头


问题描述

当spring的bean注入过程中发现循环依赖时,框架是怎么解决的,如A->B->A这种互相依赖情况时,本文基于spring 4版本


前置准备

准备2个Class,通过配置文件的方式进行配置,形成一个循环依赖的场景。我们用FileSystemXmlApplicationContext这个容器作为demo,其他容器同理

public class A {

    private B b;
}

Copy

public class B {

    A a;
}

Copy

配置文件

<?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="a" class="maven.test.A">
        <property name="b" ref="b"/>
    </bean>

    <bean id="b" class="maven.test.B">
        <property name="a" ref="a"/>
    </bean>
</beans>

Copy

入口代码

public static void main(String[] args) {

        FileSystemXmlApplicationContext applicationContext = new FileSystemXmlApplicationContext("classpath:Beans.xml");
        applicationContext.getBean("a");
    }

Copy

在初始化容器时我们主要关注两个地方,首先用map的方式去保存bean的元数据 beanDefinition,然后非懒加载方式的bean会在初始化时进行注入,整个流程都在refresh()方法中,依赖注入的地方在下面这个方法里

image.png

一步步debug下去,会发现是在DefaultListableBeanFactory的getBean中去进行注入,其实跟懒加载的入口是一样的。容器底层是用的DefaultListableBeanFactory作为具体产生bean的工厂实现,具体继承关系这里不展开讲了,具体进行依赖注入的方法就在这个工厂继承的基类AbstractBeanFactory,直接看doGetBean方法

重点来了,在DefaultSingletonBeanRegistry这个类里,我们可以看到这样几个私有属性,也就是所谓的“三级缓存”

image.png

具体的方法

image.png

这里就能看到一个处理的流程,首先我们从singletonObjects这个缓存中拿,若没取到,并且此bean正在创建中时,从第二个缓存earlySingletonObjects中取,若没有取到则从第三个缓存singletonFactories中取,若取到了,则将bean从第三个缓存移动到earlySingletonObjects中,若都没取到,则进入创建bean的流程->createBean->doCreateBean 方法中,一步步debug看到下面这个关键方法

image.png

在这里,我们将这个a这个bean放到了singletonFactories缓存中,同时设置了回调方法,此时的bean只是一个“空壳”。接下来是去解析a的属性,发现a有个b的属性,于是上面流程同样的跑一遍,将b的空壳也放到缓存中,然后去解析b的属性,发现有个a,于是直接从缓存中取到了a,并从singletonFactories缓存中将a移动到earlySingletonObjects,此时,b的属性被注入了一个a的空壳,完成了解析,然后解析a的属性b,同样的注入b的空壳,转移b的缓存,由于java是引用传递,此时a和b各自进行初始化时就能够完成循环依赖的注入,最后将a和b移动到singletonObjects缓存中。

可以看到完成整个过程后,循环依赖问题成功解决

image.png


总结

spring使用递归的方式注入bean和解决类似循环依赖的问题,可以说非常精巧,debug的时候设置条件断点会更加容易理解整个过程!

评论区
Rick ©2018