浅谈 Spring 如何解决 Bean 的循环依赖问题

您所在的位置:网站首页 spring解决了哪些问题 浅谈 Spring 如何解决 Bean 的循环依赖问题

浅谈 Spring 如何解决 Bean 的循环依赖问题

2023-11-26 14:51| 来源: 网络整理| 查看: 265

1. 什么是循环依赖?

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

举个例子

public class AService { private BService bService; } public class BService { private AService aService; }

上述例子中 AService 依赖了 BService,BService 也依赖了 AService,这就是两个对象之间的相互依赖。当然循环依赖还包括 自身依赖、多个实例之间相互依赖。

image.png

正常运行上面的代码调用 AService 对象并不会出现问题,也就是说普通对象就算出现循环依赖也不会存在问题,因为对象之间存在依赖关系是很常见的,那么为什么被 Spring 容器管理后的对象会出现循环依赖问题呢?

2. Spring Bean 的循环依赖问题

被 Spring 容器管理的对象叫做 Bean,为什么 Bean 会存在循环依赖问题呢?

想要了解 Bean 的循环依赖问题,首先需要了解 Bean 是如何创建的。

2.1 Bean 的创建步骤

为了能更好的展示出现循环依赖问题的环节,所以这里的 Bean 创建步骤做了简化:

在创建 Bean 之前,Spring 会通过扫描获取 BeanDefinition。 BeanDefinition就绪后会读取 BeanDefinition 中所对应的 class 来加载类。 实例化阶段:根据构造函数来完成实例化 (未属性注入以及初始化的对象 这里简称为 原始对象) 属性注入阶段:对 Bean 的属性进行依赖注入 (这里就是发生循环依赖问题的环节) 如果 Bean 的某个方法有AOP操作,则需要根据原始对象生成代理对象。 最后把代理对象放入单例池(一级缓存singletonObjects)中。

上面的步骤主要是为了突出循环依赖问题,如果想了解 Bean 的完整生命周期可以看这一篇文章:浅谈 Spring Bean 的生命周期 - 掘金 (juejin.cn)

两点说明:

上面的 Bean 创建步骤是对于 单例(singleton) 作用域的 Bean。 Spring 的 AOP 代理就是作为 BeanPostProcessor 实现的,而 BeanPostProcessor 是发生在属性注入阶段后的,所以 AOP 是在 属性注入 后执行的。 2.2 为什么 Spring Bean 会产生循环依赖问题?

通过上面的 Bean 创建步骤可知:实例化 Bean 后会进行 属性注入(依赖注入)

如上面的 AService 和 BService 的依赖关系,当 AService 创建时,会先对 AService 进行实例化生成一个原始对象,然后在进行属性注入时发现了需要 BService 对应的 Bean,此时就会去为 BService 进行创建,在 BService 实例化后生成一个原始对象后进行属性注入,此时会发现也需要 AService 对应的 Bean。

image.png

这样就会造成 AService 和 BService 的 Bean 都无法创建,就会产生 循环依赖 问题。

2.3 三大循环依赖问题场景

Spring 并不能解决所有 Bean 的循环依赖问题,接下来通过例子来看看哪些场景下的循环依赖问题是不能被解决的。

AService 类

/** * @author 单程车票 */ public class AService { private BService bService; public AService() { } public AService(BService bService) { this.bService = bService; } public BService getbService() { return bService; } public void setbService(BService bService) { this.bService = bService; } }

BService 类

/** * @author 单程车票 */ public class BService { private AService aService; public BService() { } public BService(AService aService) { this.aService = aService; } public AService getaService() { return aService; } public void setaService(AService aService) { this.aService = aService; } }

测试类

/** * 测试类 * @author 单程车票 */ public class Application { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml"); AService aService = (AService) applicationContext.getBean("aService"); System.out.println("执行成功,获取AService对象为:" + aService); } }

单例作用域下的 Setter方法注入 / field属性注入 出现的循环依赖

application-context.xml 配置文件

使用 property 标签也就是 Setter方法 进行属性注入。

运行结果

image.png

可以看到 Setter方法注入方式 在 Spring 中是不会产生循环依赖问题的,这主要是靠 三级缓存 机制(下文会详细说明)。

单例作用域下的 构造器注入 出现的循环依赖

application-context.xml 配置文件

运行结果

抛出 BeanCurrentlyInCreationException 异常,说明 Spring 无法解决 构造器注入 出现的循环依赖问题。

image.png

原因:因为 构造器注入 发生在 实例化阶段,而 Spring 解决循环依赖问题依靠的 三级缓存 在 属性注入阶段,也就是说调用构造函数时还未能放入三级缓存中,所以无法解决 构造器注入 的循环依赖问题。

原型 作用域下的属性注入出现的循环依赖问题

application-context.xml 配置文件

运行结果

同样抛出 BeanCurrentlyInCreationException 异常,说明 Spring 无法解决 原型作用域 出现的循环依赖问题。

image.png

原因:因为 Spring 不会缓存 原型 作用域的 Bean,而 Spring 依靠 缓存 来解决循环依赖问题,所以 Spring 无法解决 原型 作用域的 Bean。

3. Spring 如何解决循环依赖问题?

通过上文的内容能了解到 Spring 为什么会产生循环依赖问题 以及 Spring 能解决什么场景下的循环依赖问题。

上文中也有提到过 Spring 是靠 三级缓存 来解决循环依赖问题的,接下来了解一下 什么是三级缓存 以及 解决循环依赖问题的具体流程。

3.1 三级缓存是什么? /** Cache of singleton objects: bean name to bean instance. */ private final Map singletonObjects = new ConcurrentHashMap(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3