zoukankan      html  css  js  c++  java
  • Spring 是如何解决循环依赖的?

    前言

    相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的:

    还会提示这么一句:

    Requested bean is currently in creation: Is there an unresolvable circular reference?

    老铁!这就是发生循环依赖了!

    当然这里是一个异常情况。

    在我的一篇文章中介绍如何避免 Spring 自调用事务失效,其中网友给建议,说可以在类中注入自身,然后调用,而注入自身的过程也是循环依赖的处理过程。

    下面就一起看一看,什么是循环依赖,以及 Spring 是如何解决循环依赖的?

    什么是循环依赖

    Circular dependencies

    Dependency Resolution Process

    Spring IoC 容器会在运行时检测到构造函数注入循环引用,并抛出 BeanCurrentlyInCreationException。

    所以要避免构造函数注入,可以使用 setter 注入替代。

    根据官方文档说明,Spring 会自动解决基于 setter 注入的循环依赖。

    当然在咱们工作中现在都使用 @Autowired 注解来注入属性。

    PS: @Autowired 是通过反射进行赋值。

    这里从我们最经常使用的场景切入,看 Spring 是如何解决循环依赖的?

    代码

    @Service
    public class CircularServiceA {
    
        @Autowired
        private CircularServiceB circularServiceB;
    }
    
    @Service
    public class CircularServiceB {
    
        @Autowired
        private CircularServiceC circularServiceC;
    }
    
    @Service
    public class CircularServiceC {
    
        @Autowired
        private CircularServiceA circularServiceA;
    
    }
    

    这里有 A、B、C 三个类,可以看到发生了循环依赖:

    循环依赖

    但是即使发生了循环依赖,我们依然可以启动 OK,使用并没有任何影响。

    Spring 是如何解决循环依赖的

    Spring 单例 Bean 的创建 中介绍介绍了使用三级缓存。

    singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

    earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

    singletonFactories: 三级缓存,存储 singletonFactory。

    当然,这里看着比较长,可以简化一下:

    通过 Debug 来说明生成过程

    从 preInstantiateSingletons 方法开始:

    添加断点 beanName.equals("circularServiceA")

    启动Debug:

    Start

    会从缓存中获取单例 Bean

    这里很显然获取不到,继续执行,创建单例实例

    发现是单例再次获取

    这里还会从一级缓存获取一次 circularServiceA , 没有获取到,将 circularServiceA 添加到在创建的池子里面 (singletonsCurrentlyInCreation 是一个 set 集合)。

    然后会调用工厂方法 createBean(beanName, mbd, args) 创建对象。

    createBean 方法

    在 createBean 中去实例化 Bean 。

    判断是否是循环引用,是的话需要添加到三级缓存中。

    添加到三级缓存

    circularServiceA 不在一级缓存中,则将 circularServiceA 的 singletonFactory 添加到 三级缓存 (singletonFactories) 中,同时从二级缓存中移除。

    到这一步为止,circularServiceA 已经在三级缓存中了。

    开始对 Bean 的属性进行赋值。

    属性赋值

    在 populateBean 方法中执行到

    PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);

    就会对属性进行赋值

    属性赋值

    在 injet 方法中,回去解决相关依赖。

    解决依赖

    继续 Debug ,发现解决依赖,最后发现其实又调用回 beanFactory.getBean(beanName);

    不过这次创建的是 circularServiceB

    下面是调用链:

    调用链

    circularServiceB 的过程和 circularServiceA 的一样,也是创建了三级缓存,然后去创建 circularServiceC

    singletionFactories

    这时候三级缓存里面有它们三个的 singletonFactory 。

    circularServiceC 也调用到 doGetBean 方法去获取 circularServiceA

    不过这次 调用到 Object sharedInstance = getSingleton(beanName); 的时候, circularServiceA 已经存在了。

    这次调用虽然没有从一级缓存 (singletonObjects) 中获取到 circularServiceA,但是 circularServiceA创建中,所以进入判断

    在这里执行完之后, circularServiceA 从三级缓存升级到二级缓存

    使用反射对 circularServiceC 中的 circularServiceA 进行赋值, 此时 circularServiceA 是在 二级缓存中。

    那就比较好奇了,这时候 circularServiceC 里面的 circularServiceA 已经通过反射赋值,这个赋值给的是什么值?

    查看代码:

    这块是从三级缓存(singletonFactories)中获取的 singletonObject,然后调用

    singletonObject = singletonFactory.getObject();

    获取的一个对象

    这里获取到的是 circularServiceA 的引用,注意 circularServiceA 这时候还没创建完成,只是引用。所以这里赋值的是 circularServiceA 的引用。

    到这里 circularServiceC 就创建完了。

    然后会将 C 添加到一级缓存和已注册列表中,同时从二级三级缓存中删除 C。

    继续执行 B 和 A 的属性赋值以及后续的初始化流程。

    至此,循环依赖解决完毕。

    总结

    Spring 使用三级缓存来解决循环依赖的问题,三级缓存分别是:

    • singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

    • earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

    • singletonFactories: 三级缓存,存储 singletonFactory。

    本文也通过 Debug 来验证了使用三级缓存解决依赖的过程。

    不过还有一些问题没有说明:

    1. 循环依赖和代理之间的关系是什么?比如 @Transactional 和 @Async 注解会对循环依赖产生什么影响?
    2. 为什么要用三级缓存?二级缓存不可以么?

    相关推荐

  • 相关阅读:
    Oracle 函数
    ecplise 修改编码
    mysql json 使用 类型 查询 函数
    java 子类强转父类 父类强转子类
    java UUID
    Java Number & Math 类
    java String类
    apache StringUtils 工具类
    apache ArrayUtils 工具类
    java Properties
  • 原文地址:https://www.cnblogs.com/liuzhihang/p/spring-circular.html
Copyright © 2011-2022 走看看