深入理解Spring之DI循环依赖

298 阅读3分钟

什么是DI

Spring在半实例化Bean之后,填充对象属性的时候,利用IOC容器来获取所需的实例并填充到对象属性。 DI旨在对象的创建过程中动态的为其注入属性。

DI循环依赖

多个实体之间相互依赖并形成闭环的情况就叫做『循环依赖』,也叫做『循环引用』。如果不使用合理的方式去处理,就会造成死循环但Spring内部已通过三级缓存解决了这个问题

循环依赖举例说明

假设现在有两个类Girl、Boy需要被注册到SpringIOC容器中,并且这两个类都将对方作为自己内部的属性,因为双方都是对方的同桌的你。

代码示例

/** 女孩 */
@Component
public class Girl {
    //同桌的你
    @Autowired
    private Boy boy;
}
@Component
public class Boy {
    //同桌的你
    @Autowired
    private Girl girl;
}

很明显,上面的代码中,Boy依赖于Girl,Girl同样依赖于Boy,形成了一个死循环。

截屏2022-12-16 18.57.34.png

Spring三级缓存如何处理循环依赖问题

  • 一级缓存:最终存储完整单例Bean的容器,即完成实例化和初始化的Bean,称之为『一级缓存』
  • 二级缓存:缓存被其他对象引用了的半成品对象,即未完成初始化但被引用的Bean,称为『二级缓存』
  • 三级缓存:单例Bean的工厂集合,缓存未被引用的半成品对象,需要使用时通过工厂创建Bean,称为『三级缓存』

Bean缓存的顺序: 三级缓存 -> 二级缓存 -> 一级缓存

Bean获取单例的顺序: 一级缓存 -> 二级缓存 -> 三级缓存

结合Spring的三级缓存后,上述示例代码的执行机制

  1. 创建Boy实例(半实例化,这个时候会先放到第三级缓存中)
  2. 当需要注入Girl时,创建Girl实例然后放到第三级缓存中,后续执行Girl初始化工作
  3. Girl从三级缓存中拿到半实例化的Boy实例,然后为自己的属性注入。
  4. 现在Boy已经被引用了,所以现在从三级缓存移到二级缓存
  5. 当Girl实例化完成后,Girl会存储到一级缓存,并将其从二级、三级缓存中清除
  6. 回溯到Boy为其注入创建完成的Girl,完成实例化创建后,从二级缓存移动到一级缓存

截屏2022-12-16 19.41.22.png

三级缓存容器源码

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    //一级缓存
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    //二级缓存
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
    //三级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

程序执行过程

  1. 创建Boy对象

截屏2022-12-16 20.56.16.png

  1. 将创建但未初始化的Boy对象放入第三级缓存singletonFactories中

1.png

  1. Boy中的属性要注入一个Girl对象,所以创建一个Girl

截屏2022-12-16 21.03.07.png

  1. 将创建但末初始化的Girl对象存到第三级缓存singletonFactories中

截屏2022-12-16 21.04.37.png

  1. Boy会被放到二级缓存earlySingletonObjects中,并为Girl的Boy对象属性注入Boy实例。

截屏2022-12-16 21.13.06.png

  1. 将创建完成的Girl存储到一级缓存中,并删除二级、三级缓存中的数据

截屏2022-12-16 21.15.32.png

  1. 当Boy为Girl属性注入对象并完整创建后,将其放入一级缓存,并删除二三级缓存中Boy的数据

截屏2022-12-16 21.19.27.png

  1. 循环依赖结束,不会造成死循环。