写点什么

Spring 如何解决循环依赖

作者:IT巅峰技术
  • 2022 年 4 月 10 日
  • 本文字数:3098 字

    阅读完需:约 10 分钟

一、背景

我们都知道 Spring 可以通过 xml,或者解析我们的注解,通过扫描所有资源文件,从而将所有匹配到的资源封装成为一个 BeanDefinition 注册到我们的 BeanFactory 中。此时,Spring 已经知道了所有我们想要注册到容器中的 BeanDefinition,下一步就是将 BeanDefinition 实例化,这样才能提供出来给我们使用。

二、Spring 中 Bean 的实例化

我们发现 Spring 整个加载过程都在 AbstractApplicationContext.refresh()中去完成。


public void refresh() throws BeansException, IllegalStateException {    synchronized (this.startupShutdownMonitor) {    // Prepare this context for refreshing. 准备刷新    prepareRefresh();        // Tell the subclass to refresh the internal bean factory.    /*     * 刷新内部BeanFactory     * ClassPathXmlApplicationContext:1.新建BeanFactory,2.解析xml,3.封装成BeanDefintion对象     * AnnotationConfigApplicationContext: 获取GenericApplicationContext中的beanFactory     */    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();        // Prepare the bean factory for use in this context.    // 为BeanFactory进行必要的准备工作    prepareBeanFactory(beanFactory);        try {        // Allows post-processing of the bean factory in context subclasses.        // 进行额外的后置处理        postProcessBeanFactory(beanFactory);            // Invoke factory processors registered as beans in the context.        // 执行1.BeanDefinitionResgistryPostProcessor、2.BeanFactoryPostProcessor的回调        invokeBeanFactoryPostProcessors(beanFactory);            // Register bean processors that intercept bean creation.        // 实例化所有实现了BeanPostProcessor接口的类并注册到容器中去        registerBeanPostProcessors(beanFactory);            // Initialize message source for this context. 国际化        initMessageSource();            // Initialize event multicaster for this context. 初始化事件类        initApplicationEventMulticaster();            // Initialize other special beans in specific context subclasses. 子容器自定义实现        onRefresh();            // Check for listener beans and register them. 注册事件        registerListeners();            // Instantiate all remaining (non-lazy-init) singletons.        //1.bean实例化,2.ioc 3.注解支持 4.BeanPostProssor执行 5.AOP入口        finishBeanFactoryInitialization(beanFactory);            // Last step: publish corresponding event.        finishRefresh();      } catch (BeansException ex) {        if (logger.isWarnEnabled()) {            logger.warn("Exception encountered during context initialization - " +                    "cancelling refresh attempt: " + ex);        }            // Destroy already created singletons to avoid dangling resources.                    destroyBeans();        // Reset 'active' flag.                    cancelRefresh(ex);        // Propagate exception to caller.        throw ex;                } finally {        // Reset common introspection caches in Spring's core, since we        // might not ever need metadata for singleton beans anymore...        resetCommonCaches();    }}
复制代码


我们着重关注一下 finishBeanFactoryInitialization 方法,它是 Spring 实例化的入口方法。


  • 获取 BeanFactory 中所有的 beanDefinition 名称

  • 合并 RootBeanDefinition

  • 非抽象的,单例的,非懒加载的就实例化

  • 是否实现了 FactoryBean 接口,如果是加一个 &前缀调用内部的 getObject,否则直接获取

  • 首先尝试从缓存中获取 getSingleton(beanName),(首次获取必然获取不到)接着进入创建方法

  • 单例创建之前的操作:加入到正在创建的一个 set 集合中 singletonsCurrentlyInCreation

  • 调到外部的匿名类中的实例化方法,如果有值已经创建成功 singletonFactory.getObject();

  • 调到 doCreateBean 创建实例 BeanWrapper

  • 允许早期引用加入单例工厂直接返回这个 bean 的引用。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

  • 填充属性的值 populateBean

  • initializeBean


三、Spring 容器如何解决循环依赖

什么是循环依赖



循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 CircleA 引用 C ircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,则它们最终反映为一个环。


@Componentpublic class CircleClassA {public CircleClassA() {        System.out.println("====CircleClassA====");    }}
@Componentpublic class CircleClassB {public CircleClassB() { System.out.println("====CircleClassB===="); }}
复制代码


首先我们需要明确的一点是:Spring 只会处理上述类型的循环依赖(单例,非构造函数注入)其它情况直接报错。


Spring 在处理 Bean 实例化的过程中是如何解决循环依赖的呢?我们需要着重关注如下 3 个 Map。


singletonObjectsearlySingletonObjectssingletonFactories
复制代码


具体步骤如下:


  1. CircleClassA 在实例化的时候 首先从缓存中获取不到,然后进入创建方法,接着将 CircleClassA 加入到 singletonsCurrentlyInCreation 中,并在 singletonFactories 加入一个 getEarlyBeanReference,表示当前 CircleClassA 正在创建中。

  2. 当 CircleClassA 填充属性的值 populateBean 时,发现依赖了 CircleClassB,触发 CircleClassB 的实例化。

  3. 实例化 CircleClassB,首先从缓存中获取不到,然后进入创建方法,接着将 CircleClassB 加入到 singletonsCurrentlyInCreation 中,并在 singletonFactories 加入一个 getEarlyBeanReference,表示当前 CircleClassB 正在创建中。

  4. 当 CircleClassB 填充属性的值 populateBean 时,发现依赖了 CircleClassA,触发 CircleClassA 的实例化。

  5. 再次进入 CircleClassA 的实例化方法,此时虽然 singletonObjects 中获取不到 CircleClassA,但是检测到 CircleClassA 存在早期暴露的实例因此尝试从 earlySingletonObjects 中获取,首次调用获取不到从 singletonFactories 中获取,取到之后将 CircleClassA 放入 earlySingletonObjects,并提供给 CircleClassB 填充属性的值 populateBean 时使用。(此时的 CircleClassA 只是个引用的地址,实际上并不是一个完整的 CircleClassA)。

  6. 此时 CircleClassB 已经完成了(内部依赖的 CircleClassA 是个不完整的实例)并提供给 CircleClassA 填充属性的值 populateBean 时使用。CircleClassA 完成了 CircleClassB 的注入,它变成了一个完整的实例。

  7. 又由于 CircleClassB 中引用了 CircleClassA 的一个地址。所以它也同时变成了一个完整的。

  8. 实例化完成之后删除早期引用 map,并放入单例 map 中缓存 singletonObjects。





程序员的核心竞争力其实还是技术,因此对技术还是要不断的学习,关注 “IT 巅峰技术” 公众号 ,该公众号内容定位:中高级开发、架构师、中层管理人员等中高端岗位服务的,除了技术交流外还有很多架构思想和实战案例。


作者是 《 消息中间件 RocketMQ 技术内幕》 一书作者,同时也是 “RocketMQ 上海社区”联合创始人,曾就职于拼多多、德邦等公司,现任上市快递公司架构负责人,主要负责开发框架的搭建、中间件相关技术的二次开发和运维管理、混合云及基础服务平台的建设。

发布于: 刚刚阅读数: 2
用户头像

一线架构师、二线开发、三线管理 2021.12.07 加入

Redis6.X、ES7.X、Kafka3.X、RocketMQ5.0、Flink1.X、ClickHouse20.X、SpringCloud、Netty5等热门技术分享;架构设计方法论与实践;作者热销新书《RocketMQ技术内幕》;

评论

发布
暂无评论
Spring如何解决循环依赖_IT巅峰技术_InfoQ写作平台