Spring 循环依赖原理及解决方案

news/2024/12/22 13:46:41/

一、什么是循环依赖

循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
举例:

java">@Component
public class AService {// A中注入了B@Autowiredprivate BService bService;
}@Component
public class BService {// B中也注入了A@Autowiredprivate AService aService;
}

上述例子中 AService 依赖于 BService,BService 也依赖了 AService,这就是两个对象之间互相依赖。循环依赖还包括 身依赖、多个实例之间相互依赖(A依赖于B,B依赖于C,C又依赖于A)。

在普通Java环境下正常运行上面的代码调用 AService 对象并不会出现问题,也就是说普通对象就算出现循环依赖也不会存在问题,因为可以将属性设置为null,那么为什么被 Spring 容器管理后的对象有循环依赖的情况会出现问题呢?

二、Spring 可以解决哪些循环依赖问题

在Spring 中,只有同时满足以下两点才能解决循环依赖的问题:

  1. 依赖的 Bean 必须都是单例
  2. 依赖注入的方式,必须不全是构造器注入,且 beanName字母序在前的不能是构造器注入

1.为什么必须是单例

如果两个Bean都是原型模式的话,那么创建A1需要创建一个B1,创建B1的时候要创建一个A2,创建A2又要创建一个B,创建B2又要创建一个A3,创建A3又要创建—个B3…
就又卡BUG了。因为原型模式都需要创建新的对象,不能用以前的对象。

如果是单例的话,创建A需要创建B,而创建的B需要的是之前的那个A。也是基于这点,Spring 就能操作操作了。具体做法就是:先创建A,此时的A是不完整的(没有注入B),用个map保存这个不完整的A,再创建B,B需要A,所以从那个map得到不完整"的A,此时的B就完整了,然后A就可以注入B,然后A就完整了,B也完整了,且它们是相互依赖的。

2.为什么不能全是构造器注入

在 Spring 中创建 Bean 分三步(详细见Spring Bean 的生命周期):

  1. 实例化, createBeanInstance,就是 new了个对象
  2. 属性注入, populateBean,就是 set 一些属性值
  3. 初始化, initializeBean,执行一些 aware 接口中的方法,init-Method,AOP代理等

明确了上面这三点,再结合我上面说的“不完整的”,我们来理一下。
如果全是构造器注入,比如 A(B b),那表明在new A 的时候,就需要得到 B,此时需要new B,但是 B 也是要在构造的时候注入 A,即B(A a),这时候 B 需要在一个 map 中找到不完整的 A,发现找不到。为什么找不到? 因为 A 还没 new 完呢,所以找不到完整的A,因此如果全是构造器注入的话,那么 Spring 无法处理循环依赖。而如果 A 是通过 set 注入 B 的,那么 B 在属性注入时就能注入不完整的 A 了(因为 A 虽然没有进行属性注入,但是已经实例化了),因此 B 就能完整创建 Bean,B 创建完,A 也能进行属性注入了,因此就解决了循环依赖。

在这里插入图片描述

三、Spring 如何解决循环依赖问题

1、三级缓存

  1. 一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)
  2. 二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行属性注入和初始化的Bean。
  3. 三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存。

2、三个缓存如何配合解决循环依赖问题?

关键就是提前暴露未完全创建完毕的Bean。

  1. 首先,获取单例 Bean 的时候会通过 BeanName 先去一级缓存查找完整的 Bean,如果找到则直接返回,否则进行步骤2。
  2. 看对应的 Bean 是否在创建中,如果不在直接返回找不到,如果是,则会去二级缓存查找 Bean,如果找到则返回,否则进行步骤3。
  3. 三级缓存通过 BeanName 查找到对应的工厂,如果存在工厂则通过工厂创建 Bean,并且放置到二级缓存中。
  4. 如果三个缓存都没找到,则返回null。

从上面的步骤我们可以得知,如果查询发现 Bean 还未创建,到第二步就直接返回 null,不会继续查二级和三级缓存。

返回 null 之后,说明这个 Bean 还未创建,这个时候会标记这个 Bean 正在创建中,然后再调用 createBean 来创建 Bean,而实际创建是调用方法 doCreateBean。
doCreateBean 这个方法就会执行上面我们说的三步骤:实例化、属性注入、初始化。

在实例化 Bean 之后,会往三级缓存塞入一个工厂,而调用这个工厂的 getObject 方法,就能得到这个 Bean。

3、举例说明

举例:A、B 两个类循环依赖,Spring 结合三级缓存这样解决:

  1. 一开始创造 A 时候查询—级缓存(里面存成品),发现没找到则看二级缓存是否在创建中(有没有半成品)。都没有则需要创建 A 的 bean,调用的是 createBean,过程分别是实例化、属性注入、初始化。
  2. A 实例化之后往三级缓存加入一个 A 的 getObject 方法,这个就是解决循环依赖的关键。
  3. 到了属性注入,因为 A 依赖 B 因此需要创建 B 。同样的路线 B 也要 createBean 。不一样的也是解决循环依赖的一环:到了属性注入,查询二级缓存的 A 为创建中,则调用三级缓存的工厂 getObject 创建一个半成品的 A,放入到二级缓存中,并完成 B 的第二步属性注入。
  4. 后面初始化 initializeBean,完成 B 的 Bean 创建,放到—级缓存。
    回到 A 刚刚卡在属性注入,现在可以成功注入 B,然后初始化,A 也完成了 Bean 创建。(注︰成品和半成品就是没有注入所需的依赖)

为什么 A 中注入的 B 是构造器注入就不能解决?

再次结合实例解释一下:
如果 A 是构造器注入,B 是 set 注入。则说明 A 需要 B 的时刻提前了,在实例化new A(B b) 的时候就需要 B。此时 A 没有往三级缓存放 getObject,因此到了创建依赖 B 的时候,无法获取 A 的 getObject 工厂方法,只能继续 new A,造成循环依赖的死循环。

4、三级缓存有必要吗?

在上述的步骤中,三级缓存的作用就是直接返回实例化的 A,去掉三级缓存,直接从二级缓存中取出 A 也可以。所以理论上通过二级缓存就能解决循环依赖。那么三级缓存的作用是什么?

三级缓存的作用体现在:在 A 被代理的情况下,三级缓存里的 Bean 工厂有返回代理对象的能力,可以控制:1)当没有循环依赖时,会按照 Bean 的生命周期来创建 Bean(即不会用到三级缓存,在初始化后的后处理器中才会进行代理)2)当有循环依赖时,通过三级缓存提前将代理对象返回给 B。(此时的代理对象是还未进行属性注入的代理对象)


http://www.ppmy.cn/news/1524436.html

相关文章

得物超级品质保障中心,助力电商品质保障迈向新高度

近期记者走进国内知名潮流电商平台——得物App的超级品质保障中心。该中心位于上海市嘉定区,总建筑面积达到约12万平方米,是集查验鉴别、鉴别研究、质量管理、仓储流转等功能于一体的综合性服务设施,全面覆盖了服装、配饰、奢侈品等多个业务品…

APACHE-ATLAS-2.1.0 - 基础运维

(一)SOLR相关 1. 如何创建/删除集合? # 1. 删除 solr/bin/solr delete -c vertex_index solr/bin/solr delete -c edge_index solr/bin/solr delete -c fulltext_index# 2. 创建 solr/bin/solr create -c vertex_index -force -d conf/solr…

从零开始学cv-0:图像处理基础知识

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一,图像分类1.1、模拟图像1.2、数字图像 二、颜色模式(颜色存储)2.1、RGB模式(发光模式)2.2、CMYK模…

pptp解说

PPTP(Point-to-Point Tunneling Protocol)是一种网络协议,用于在互联网上创建虚拟专用网络(VPN)连接。 PPTP 允许远程用户通过公共网络(如互联网)安全地连接到私有网络。它是一种较早的VPN技术…

鸿萌数据恢复服务:Mac 文件系统是如何影响 Mac 数据恢复的?

天津鸿萌科贸发展有限公司从事数据安全服务二十余年,致力于为各领域客户提供专业的数据备份、数据恢复解决方案与服务,并针对企业面临的数据安全风险,提供专业的相关数据安全培训。 公司是多款国际主流数据恢复软件的授权代理商,为…

C++设计模式——State状态模式

一,状态模式的定义 状态模式是一种行为型设计模式,状态模式允许对象在内部状态发生切换时改变它自身的行为。 状态模式的主要目的是将复杂的状态切换逻辑抽象化为一组离散的状态类,使代码结构更加清晰和易于维护。 状态模式将对象的行为封…

继收购西门子物流自动化后,丰田又投资一家AGV公司,智能物流版图已极其夸张...

导语 大家好,我是社长,老K。专注分享智能制造和智能仓储物流等内容。 继成功将西门子物流自动化(机场物流业务)纳入麾下后,丰田并未停下其征伐的步伐,而是再度出手,与新兴科技巨头Gideon携手,共同绘制了一幅…

AI 产品经理:2024 年职场新航标 ——AI 产品经理的未来与契机

前言 这两年,AI 骤然“火”了起来,可谓出现了重大“转折”。就在这短短两年间,全球各大“大厂”几乎在同一时间争先恐后地跟进 AI 技术。从 ChatGPT 发布起,谷歌、Facebook、亚马逊等纷纷紧跟其后,国内的百度、腾讯、…