Spring Boot - IOC(四) - 循环依赖与解决方案

1. 编写测试代码

为演示循环依赖的效果,咱来编写两个组件,模拟人与猫的关系:人养猫,猫依赖人。

@Component
public class Person {
    @Autowired
    Cat cat;
}

@Component
public class Cat {
    @Autowired
    Person person;
}

之后使用包扫描来启动IOC容器:

public class App {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.example.demo.component");
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
}

运行,发现可以正常打印cat和dog:

下面来结合上一篇中的bean创建流程,分析IOC容器是如何解决循环依赖的。

【下面的源码不再使用通篇+描述,而是使用步骤来一步步描述过程,小伙伴要跟上思路来一起分析】

2. 流程全分析 - 初始化IOC容器

2.1 new AnnotationConfigApplicationContext

前面的创建和包扫描部分,会把Cat和Dog都读进 BeanFactory

下面的refresh方法:

2.2 refresh

最终会调用到11步:初始化剩余的单实例Bean

2.3 finishBeanFactoryInitialization

直接走到最后一步:preInstantiateSingletons

2.4 DefaultListableBeanFactory#preInstantiateSingletons

到此为止,开始进行真正的Bean创建。

Debug中可以看到,由于Cat在Person之前,所以先来创建Cat:

3. 流程全分析 - 初始化Cat

由上面的 getBean,跳转到 AbstractBeanFactorygetBean 方法:

紧接着调 doGetBean

3.1 doGetBean(cat)

在Lambda表达式中要调用 createBean ,但在调用之前先看一眼 getSingleton 方法:

3.2 【重要】getSingleton

这个方法来自 **DefaultSingletonBeanRegistry** ,这个类中有几个很重要的概念,就是它的几个成员(这几个成员都有文档注释):

  • **singletonObjects**:一级缓存,存放完全初始化好的Bean的集合,从这个集合中取出来的Bean可以立马返回

  • **earlySingletonObjects**:二级缓存,存放创建好但没有初始化属性的Bean的集合,它用来解决循环依赖

  • **singletonFactories**:三级缓存,存放单实例Bean工厂的集合

  • **singletonsCurrentlyInCreation**:存放正在被创建的Bean的集合

这几个成员相当重要,下面会慢慢看见他们。下面是 getSingleton 方法:

beforeSingletonCreation 方法咱之前看过了:

它把当前的 cat 放入 singletonsCurrentlyInCreation **(正在创建的Bean)**中。

接下来准备调用 singletonFactory.getObject() ,也就是调用下面的 createBean 方法:

3.3 createBean(cat)

最终调到 doCreateBean 方法:

3.4 doCreateBean(cat) & createBeanInstance

Bean的实例化过程咱就不看了,当 createBeanInstance 方法运行完后,此时的cat中:

img

此时:这个cat被称为 “早期Bean” ,而且被包装为 BeanWrapper

继续往下走,中间有一个非常关键的步骤:earlySingletonExposure 的判断。

3.5 earlySingletonExposure的判断 & addSingletonFactory

这个判断非常关键,它要同时成立三个条件才能进if结构:

  • 这个Bean是一个单实例Bean

  • IOC容器允许循环依赖(默认是true)

  • 正在创建的单实例Bean对象中有当前的这个Bean

由于在3.2环节中,singletonsCurrentlyInCreation 这个集合中已经把 cat 放进去了,此时这个判断也为true

三个条件全为true,进入if结构中,它干了这么一件事:

来看 addSingletonFactory 的源码:

这一步的动作可以看出来,是将当前正在创建的Bean保存到三级缓存中,并从二级缓存中移除(由于本来二级缓存中没有,故可以只认定为放入三级缓存)。


下面的属性赋值&自动注入点:

3.6 populateBean(cat)

在这个 InstantiationAwareBeanPostProcessor 的for循环中,会调用 AutowiredAnnotationBeanPostProcessorpostProcessProperties 方法,触发自动注入。

3.7 AutowiredAnnotationBeanPostProcessor#postProcessProperties

在上面收集好要注入的属性后,下面的 metadata.inject 方法:

3.8 【注入】metadata.inject

跳转到 AutowiredFieldElement#inject 中:

一开始初始化的时候肯定找不到 Person ,要走 beanFactory.resolveDependency 方法:

3.9 beanFactory.resolveDependency

此时跳转到 DefaultListableBeanFactory 类中:

来到最后的 doResolveDependency 方法中:

3.10 doResolveDependency

Debug走到这一步,跳转进去的方法就是 getBean

4. 流程全分析 - 初始化Person

4.1 getBean(person)

继续往下走,回到 AbstractBeanFactory 了:

4.2 doGetBean(person) - getSingleton(person)

与上面的思路类似,不再贴源码,当执行到getSingleton方法时,要知道 beforeSingletonCreation 方法又执行了,此时正在创建的Bean有两个了:

img

4.3 createBean(person) - doCreateBean(person) -> addSingletonFactory

这几步操作最终完成的动作:将person放入三级缓存,并从二级缓存中移除

4.4 populateBean(person)

跟上面一样,也是同样的执行后置处理器,走inject方法。

4.5 metadata.inject - resolveDependency - doResolveDependency

最终也会像上面一样,执行到这一步:

进去会调getBean(cat)。

4.6 再次getBean(cat)

其实这里进的还是我们熟悉的那个getBean:

下面还是那一套,不过进入 doGetBean 方法后有一个很重要的环节:**getSingleton**

4.7 【二次获取】getSingleton(cat)

注意在这里第二次获取 cat 的时候,由于现在 正在被创建的Bean 中有 cat 了,所以 isSingletonCurrentlyInCreation(cat) 将返回true!会进入到下面的if结构体中!

进入之后,它要确定 **earlySingletonObjects** 二级缓存 中是否有当前创建好但没有赋值初始化的Bean(当前cat),此时根据前面的步骤,person和cat均只在三级缓存,所以取出的 singletonObject 为null,进入第二层if的结构体中。再往下来,它又从 **singletonFactories** 三级缓存 中取当前正在创建的Bean(cat),这次可以查到,于是进入第三层if的结构体。它干了两件事:将这个 cat 放入二级缓存,并从三级缓存中移除

操作完成后的状态:

img

那既然这里已经获取到了,那 singletonObject 自然有值,就可以正常返回那个 正在创建,但还没有注入依赖项的cat

4.8 回到doGetBean(cat)

获取到 cat 后,下面会调用一个 getObjectForBeanInstance 方法:

4.9 getObjectForBeanInstance(cat)

这里先通过 this.currentlyCreatedBean.get() 取到当前线程中正在创建的Bean的名称,发现为null(到目前为止也没发现谁在操作它,通过IDEA的提示,发现是 obtainFromSupplier 方法中有对它的操作,之前提过了我们不关心它),则直接调父类的 getObjectForBeanInstance 方法:

4.10 AbstractBeanFactory#getObjectForBeanInstance(cat)

第一段if中,因为 cat 不是被工厂引用的Bean,这部分不进入。

第二段if中,因为 cat 不是一个工厂Bean,前半段返回true,直接返回cat。

这段方法走完后,cat还是那个cat。

回到doGetBean方法:

4.11 再回到doGetBean(cat)

这一段if判断是确定bean与返回的类型是否一致,这里很明显一致,直接强转返回即可。

4.12 回到注入的部分(person)

descriptor.resolveCandidate 方法执行完后,下面把bean交给result,确定没问题,返回出去。

4.13 回到resolveDependency(person)

这个方法也就成功返回cat了。

4.14 返回inject方法(person)

取到value,也就是那个cat的Bean后,最底下利用反射赋值,自动注入结束。

此时二级缓存和三级缓存中还是那个状态:

img

4.15 回到doCreateBean(person)

person的属性赋值和自动注入完成后,执行初始化方法(没定义),最后返回出去。

4.16 回到createBean(person)

也是直接返回出去。

4.17 回到DefaultSingletonBeanRegistry#getSingleton(person)

createBean 返回后回到Lambda表达式,又回到 getSingleton 方法中。创建的这个单实例Person会被 newSingleton 标记为true,在下面的finally块中,要执行两个重要的方法:afterSingletonCreationaddSingleton

4.18 afterSingletonCreation

这部分的作用:将创建好的Bean从“正在创建中的Bean”中移除

4.19 【重要】addSingleton

这部分的作用:将创建的这个Bean放入一级缓存,从二级缓存和三级缓存中移除,并记录已经创建了的单实例Bean

至此,Person的创建完全结束。

5. 回到Cat的创建

5.1 回到DependencyDescriptor#resolveCandidate(cat)

这个 getBean(person) 结束了,真正完全创建好的Person也返回来了。下面的步骤就与上面一样了,快速过一遍。

5.2 返回注入的部分(cat)

5.3 回到resolveDependency(cat)

5.4 返回inject方法(cat)

5.5 回到doCreateBean(cat)

5.6 回到createBean(cat)

5.7 回到DefaultSingletonBeanRegistry#getSingleton(cat)

5.8 afterSingletonCreation

5.9 【重要】addSingleton

至此,Cat的创建完全结束。

6. @Autowired解决循环依赖的核心思路

整个IOC容器解决循环依赖,用到的几个重要成员:

  • **singletonObjects**:一级缓存,存放完全初始化好的Bean的集合,从这个集合中取出来的Bean可以立马返回

  • **earlySingletonObjects**:二级缓存,存放创建好但没有初始化属性的Bean的集合,它用来解决循环依赖

  • **singletonFactories**:三级缓存,存放单实例Bean工厂的集合

  • **singletonsCurrentlyInCreation**:存放正在被创建的Bean的集合

咱来总结一下,IOC容器解决循环依赖的思路:

  1. 初始化 Bean 之前,将这个 bean 的 name 放入三级缓存

  2. 创建 Bean 将准备创建的 Bean 放入 singletonsCurrentlyInCreation (正在创建的 Bean )

  3. createNewInstance 方法执行完后执行 addSingletonFactory,将这个实例化但没有属性赋值的 Bean 放入三级缓存,并从二级缓存中移除

一般情况下初次创建的 bean 不会存在于二级缓存,故该步骤可以简单理解为仅仅是放入了三级缓存而已

  1. 属性赋值&自动注入时,引发关联创建

  2. 关联创建时:

    1. 检查“正在被创建的 Bean ”中是否有即将注入的 Bean

    2. 如果有,检查二级缓存中是否有当前创建好但没有赋值初始化的 Bean

    3. 如果没有,检查三级缓存中是否有正在创建中的 Bean

    4. 至此一般会有,将这个 Bean 放入二级缓存,并从三级缓存中移除

  3. 之后 Bean 被成功注入,最后执行 addSingleton,将这个完全创建好的Bean放入一级缓存,从二级缓存和三级缓存移除,并记录已经创建了的单实例Bean

下面的这张图描述了上述的过程,图很大,建议用原图查看更佳:

img

最后更新于

这有帮助吗?