Spring不能处理的循环依赖

news2024/11/24 7:34:01

文章目录

      • 场景一:prototype 类型的循环依赖
      • 场景二: constructor 注入的循环依赖
      • 场景三:普通的 AOP 代理 Bean 的循环依赖–默认是可以的
      • 场景四:@Async 增强的 Bean 的循环依赖
      • 总结


参考:https://blog.csdn.net/wang489687009/article/details/120546430

Spring使用三级缓存解决了循环依赖的问题,但是在某些情况下,它解决不了循环依赖问题,导致项目启动报错。

场景一:prototype 类型的循环依赖

描述:

A --> B --> A,且 A、B 都是 scope=prototype(原型模式

@Service
@Scope("prototype")
public class A {
    @Autowired
    private B b;
}

@Service
@Scope("prototype")
public class B {
    @Autowired
    private A a;
}

这种场景下会报错:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'c1Service': Unsatisfied dependency expressed through field 'c2Service'; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'c2Service': Unsatisfied dependency expressed through field 'c1Service'; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c1Service': Requested bean is currently in creation: Is there an unresolvable circular reference?

分析:

A 实例创建后,populateBean 时,会触发 B 的加载。
B 实例创建后,populateBean 时,会触发 A 的加载。由于 A 的 scope=prototype,从缓存中获取不到 A,要创建一个全新的 A。

这样,就会进入一个死循环。所以Spring提前进行了检查,并抛出了异常。

在这里插入图片描述

解决:

在需要循环注入的属性上添加 @Lazy

场景二: constructor 注入的循环依赖

描述: A --> B --> A,且 都是通过构造函数依赖的


@Service
public class A {
    private B b;
    
    public A(B b) {
        this.b=b;
    }
}

@Service
public class B {
    private A a;
    
    public B(A a) {
        this.a=a;
    }
}

这种场景下会报如下错:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'c1' defined in file [/target/classes/com/kvn/beans/circle/constructor/C1.class]: Unsatisfied dependency expressed through constructor parameter 0; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'c2' defined in file [/target/classes/com/kvn/beans/circle/constructor/C2.class]: Unsatisfied dependency expressed through constructor parameter 0; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c1': Requested bean is currently in creation: Is there an unresolvable circular reference?

分析
A 实例在创建时(createBeanInstance),由于是构造注入,这时会触发 B 的加载。
B 实例在创建时(createBeanInstance),又会触发 A 的加载,此时,A 还没有添加到三级缓存中,所以就会创建一个全新的 A。
这样,就会进入一个死循环。所以Spring提前进行了检查,并抛出了异常

在这里插入图片描述

解决:

在需要循环注入的属性上添加 @Lazy

例如:

public A(@Lazy B b){...}

场景三:普通的 AOP 代理 Bean 的循环依赖–默认是可以的

描述:

A --> B --> A, 且 A,B 都是普通的 AOP Proxy 类型的 bean

普通的 AOP proxy 类型指:通过用户自定义的 @Aspect 切面生成的代理 bean,区别于 @Async 标记的类产生的 AOP 代理

Spring 默认解决了 普通的 AOP 代理 Bean 的循环依赖 问题

分析:
通常情况下, AOP proxy 的创建是在 initializeBean 的时候,通过 BeanPostProcessor 处理的。
A 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 B 的加载。
B 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 A 的加载,这时,三级缓存中有 A,那么通过三级缓存 ObjectFactory#getObject() 可以获取到 bean 的早期引用。

获取 bean 的早期引用的逻辑如下:

// AbstractAutowireCapableBeanFactory#getEarlyBeanReference()  
/**
 * Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
 * 获取指定 bean 的早期引用,通常用于解析循环引用。
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,而 AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor,所以,在通过三级缓存 getEarlyBeanReference() 的时候,就可以提前获取到最终暴露到 Spring 容器中的代理 bean 的早期引用。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    ......
}

所以,普通的 AOP 代理 bean 的循环依赖是没有问题的。

场景四:@Async 增强的 Bean 的循环依赖

先说结论:

@Async注解标注的bean在循环依赖发生时,不会提前生成代理对象,而是返回原始bean对象给其他bean引用,这时在执行后置处理器逻辑时又会为当前bean生成了一个代理bean对象,最终导致生成的代理bean对象和其他bean所引用的原始bean对象不一致导致报错。

描述:

A --> B --> A, 且 A 是被 @Async 标记的类

@Service
public class A {
    @Autowired
    private B b;
    
    @Async
    public void m1(){
    }
}

@Service
public class B {
    @Autowired
    private A a;
}

这种场景会报如下错:

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'p1Service':
Bean with name 'p1Service' has been injected into other beans [p2Service] in its raw version as part of a circular reference, but has eventually been wrapped.
This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

分析:
proxy 的创建是在 initializeBean 的时候,通过 BeanPostProcessor 处理的。
A 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 B 的加载。
B 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 A 的加载,这时,三级缓存中有 A,那么通过三级缓存 ObjectFactory#getObject() 可以获取到 bean 的早期引用。

普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor。
而 @Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,AbstractAdvisingBeanPostProcessor 没有实现 SmartInstantiationAwareBeanPostProcessor。

public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
    .......
}

所以,这时通过 A 的三级缓存来获取 bean 的早期引用时,获取到的是 bean 的原始对象的引用,而不会提前生成代理对象
这时 B 中注入的 A 对象不是代理对象。最后会导致 B 中持有的 A 对象与 Spring 容器中的 bean A 不是同一个对象。
这种情况显然是有问题的,跟我们的预期是不相符的,所以,Spring 在 initializeBean 之后,检验二级缓存中的 bean 与最终暴露到 Spring 容器中的 bean 是否是相同的,如果不同,就会报错。

在这里插入图片描述

综上,@Async 标记的类产生的代理 bean 发生了循环依赖, Spring 默认是解决不了的。

解决:

  • 方式一:在需要循环注入的属性上添加 @Lazy,比如在A类的b字段上加或者在B类的a字段上加都可以
@Service
public class A {
    @Autowired
    private B b;
    
    @Async
    public void m1(){
    }
}

@Service
public class B {
    @Autowired
    **@Lazy**
    private A a;
}

  • 方式二:在标注@Async注解方法的类上,加上@DependsOn 注解,并且value指定要依赖的属性,依旧可以启动起来,例如下面代码
@Service
@DependsOn("b")
public class A {

    @Autowired
    private B b;

    @Async
    public void test() {

    }
}

@Service
public class B {

    @Autowired
    private A a;

}

这样就会先去加载B的bean,然后再去加载A的bean,这时候A的bean可以从第三级缓存获取B的bean,然后初始化完成之后返回代理A的bean对象给到B,B也可以初始化完成。。

  • 方式三:在标注@Asnc注解方法的类上加@Lazy注解,例如
@Service
@Lazy // spring刷新容器时,会判断类上是否有@Lazy注解,有的话就不走getBean流程了,这样就保证了B先去加载,和上面的DependsOn的处理一样
public class A {

    @Autowired
    private B b;

    @Async
    public void test() {

    }
}

@Service
public class B {

		
    @Autowired
    private A a;

}

Order注解用法

一开始我还尝试用Order注解来控制A和B的加载顺序,结果发现行不通,它只能控制bean的初始化顺序

总结

Spring 为我们解决了循环依赖的问题。下面这些情况下,默认是解决不了循环依赖问题的:

  1. prototype 类型的循环依赖
  2. constructor 注入的循环依赖
  3. @Async 类型的 AOP Bean 的循环依赖

这些解决不了的场景都可以通过 @Lazy注解来解决,@Async的场景还可以通过@DependsOn注解解决

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2164364.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis系列补充:聊聊布隆过滤器(go语言实践篇)

1 介绍 布隆过滤器(Bloom Filter)是 Redis 4.0 版本之后提供的新功能,我们一般将它当做插件加载到 Redis Service服务器中,给 Redis 提供强大的滤重功能。 它是一种概率性数据结构,可用于判断一个元素是否存在于一个集…

Elasticsearch导出导入数据

1.概念回顾 2.基础操作 展示详细信息 GET:http://127.0.0.1:9200/_cat/indices?v Java代码将文件导入到ES package com.Graph.medicalgraph;import org.apache.http.HttpHost; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.act…

Java中的位图和布隆过滤器(如果想知道Java中有关位图和布隆过滤器的知识点,那么只看这一篇就足够了!)

前言:在学习之前的数据结构的时候,我们使用的数据量都不是很大,但是在生活中,我们常常面临着要处理大量数据结果并得出最终结果,那么有没有什么数据结构可以帮助我们实现这样的功能呢? ✨✨✨这里是秋刀鱼不…

Circular dependency between the following tasks(gradle循环依赖的问题)

简介 目前公司项目主要使用gradle而不是maven,所以对gradle的使用不是很清楚,遇到这个问题的时候一直在晚上查资料,但是解决方案基本都是安卓的,后续先前辈请教了一下,才找到解决方案。 问题解析 本质上就是两个模块…

【QT 5 调试软件+Linux下调用脚本shell-无法调度+目录拼写+无法找目录+sudo权限(2)+问题解决方式+后续补充】

【QT 5 调试软件Linux下调用脚本shell-无法调度目录拼写无法找目录sudo权限(2)问题解决方式后续补充】 1、前言2、问题综述:自研qt上位机无法调度脚本(1)可能原因1:无法找到目录情况说明:解决思…

程序人生:软件测试 非技术性面试题【建议每个测试人观看】

1、自我介绍:三分钟左右 2、为什么从郑州/太原离职? 3、你的职业规划是什么样的? 4、对下一家公司有什么自己的想法吗? 5、你觉得作为一名测试工程师,应该具备什么样的素养? 6、你觉得管理层&#xff…

HTML | 外部引入 CSS 的2种方式:link和@import有什么区别?

外部引入 CSS 有2种方式,link 和 import。就结论而言,强烈建议使用 link ,慎用 import 方式。 两者都是外部引用 CSS 的方式,但是存在一定的区别: (1)从属关系区别 link是HTML / XHTML标签&a…

react crash course 2024(3) jsx语法及组件

创建组件的文件结构 rafce创建组件内容 const NavBar () > {return (<div>NavBar</div>) }export default NavBar 使用该组件 组件传值 或者把props解构 设置默认值 5.用自定义组件包裹一些元素 把他们传值 元素直接变成了参数被传进来

求推荐网站建设公司?2024网站建设公司哪家好TOP3

好的网站建设公司在这个信息时代寻找起来会比较困难&#xff0c;因为你在任何一个搜索引擎搜索“网站建设”这个关键词&#xff0c;平台会给你推送成千上万家的建站公司&#xff0c;让人看得眼花缭乱&#xff0c;但究竟哪个更好&#xff0c;让人捉摸不透。 前段时间&#xff0…

多城联动、多形式开展网安周公益活动,开源网安传播网络安全知识

9月9日至15日&#xff0c;以“网络安全为人民&#xff0c;网络安全靠人民”为主题的2024年国家网络安全宣传周将在全国范围内统一开展&#xff0c;通过多样的形式、丰富的内容&#xff0c;助力全社会网络安全意识和防护技能提升。开源网安今年继续为各地企业、群众带来了丰富的…

Qt QFileDialog使用方法

头文件 #include <QFileDialog> 成员名称返回值说明getExistingDirectoryQString返回用户选中的文件夹路径getExistingDirectoryUrlQUrl与QFileDialog::getExistingDirectory()的主要区别来自于为用户提供的选择远程目录的能力getOpenFileNameQString返回用户选中的文件…

从更底层的角度理解网站的访问过程

文章目录 1.示例&#xff0c;访问www.baidu.com是如何返回数据的1.输入www.baidu.com回车2.检查本机的C:\Windows\System32\drivers\etc\hosts配置文件夹下有没有这个域名对应的映射&#xff1a; 1.示例&#xff0c;访问www.baidu.com是如何返回数据的 1.输入www.baidu.com回车…

18.2 k8s-apiserver监控源码解读

本节重点介绍 : k8s代码库和模块地址 下载 apiserver源码 apiserver中监控源码阅读 k8s源码地址分布 k8s代码库 访问github上k8s仓库&#xff0c;readme中给出了k8s 模块的代码地址举例图片 组件仓库列表 地址 Repositories currently staged here: k8s.io/apik8s.io/a…

Maven配置、下载教程(非常详细)

maven下载地址 Maven – 发行说明 – Maven 3.6.1 (apache.org) 1.配置settings.xml 下载完maven之后&#xff0c;保存在电脑中并解压 打开maven文件 -->conf-->settings.xml 使用记事本方式打开 打开之后找到这个地方&#xff0c;在电脑中创建一个文件夹(repository)…

起重机防摇摆技术如何达标-武汉正向科技

武汉正向科技防摇摆控制器 主要技术参数 1、防摇摆精度&#xff1a; 0.4 2、行车到达目标位置偏差位置偏差&#xff1a; 25mm 3、通讯方式&#xff1a;PROFINET / PROFIBUS / RS232 / RS422 / RS485&#xff1b; 4、消除载荷的摇摆达 96% 以上&#xff1b; 5、技术先进…

图像面积计算一般方法及MATLAB实现

一、引言 在数字图像处理中&#xff0c;经常需要获取感兴趣区域的面积属性&#xff0c;下面给出图像处理的一般步骤。 1.读入的彩色图像 2.将彩色图像转化为灰度图像 3.灰度图像转化为二值图像 4.区域标记 5.对每个区域的面积进行计算和显示 二、程序代码 %面积计算 cle…

- 串口通信

USART串口通信 目录 USART串口通信 回顾 USART串口通信 1、通信分类与作用 2、串口通信的相关参数&#xff08;重点&#xff09; 3、位协议层 -- RS232协议 4、STM32F103 中的串口外设 5、调试串口编程 -- &#xff08;1&#xff09;串口初始化&#xff1a;时钟、IO、…

【Git入门】使用 Git 进行项目管理:Word Count 程序开发与托管

在软件开发过程中&#xff0c;版本控制工具是不可或缺的。Git 作为一款强大的分布式版本控制工具&#xff0c;为开发者提供了高效的代码管理和协作方式。本博客将介绍如何下载安装 Git 版本管理工具&#xff0c;并使用 Git 和 GitHub 平台进行一个名为 Word Count 的项目开发与…

【鸿蒙HarmonyOS NEXT】数据存储之关系型数据库RDS

【鸿蒙HarmonyOS NEXT】数据存储之关系型数据库RDS 一、环境说明二、关系型数据库RDS介绍三、示例代码加以说明四、小结 一、环境说明 DevEco Studio 版本&#xff1a; API版本&#xff1a;以12为主 二、关系型数据库RDS介绍 1. RDS关系型数据库简介&#xff1a; 关系型数…

VMware提供虚拟硬盘并使得Oracle Linux集群共享块设备并绑定raw设备。

一、Vmware操作 nodeA : 1、创建SCSI控制器:类型为物理。 添加新磁盘。 类型为独立-持久。 nodeB: 新增磁盘,但是选择node A刚才创建的磁盘。 类型:独立-持久。 二、OS层操作 两台都要做绑定。 详细步骤 1. 创建 raw 设备节点 首先,确保 /dev/raw 目录存在。如果不…