Springboot详细讲解循环依赖

目录
  • 一、循环依赖
  • 二、循环依赖形成条件(使用构造器注入)
  • 三、循环依赖形成条件(@Aysnc注解的bean生成了对象的代理)
  • 四、针对以上问题对Spring如何解决循环依赖进行详细阐述

一、循环依赖

顾名思义多个类中的依赖形成了环路,形成了类似于死锁的情况,导致springboot在启动时无法为我们创建Bean。通俗来说 就是beanA中依赖了beanB,beanB中也依赖了beanA。

spring是支持循环依赖的,但是默认只支持单例的循环依赖,如果bean中依赖了原型bean,则需要加上lookup方法。Spring会为我们解决循环依赖。

@Autowired是通过三级缓存来解决循环依赖的。

@Autowired进行属性注入可以解决循环依赖。原理是: Spring控制了bean的生命周期,先实例化bean,后注入bean的属性。 Spring中记录了正在创建中的bean(已经实例化但还没初始化完毕的bean),所以可以再注入属性是,从记录的ben中去依赖的对象。

相对而言,单纯使用构造器注入就无法解决循环依赖。因为在构造时就需要传入依赖的对象,导致无法实例化。但是 构造器注入可以使用@Lazy解决循环依赖,在实例化时,传入代理对象,真正使用时才会生成真正的对象。

二、循环依赖形成条件(使用构造器注入)

1、使用构造方法的方式来注入依赖,并且类A中依赖类B,类B也同时依赖类A,这样两个类都无法正常进行Bean的创建,就会抛出异常:BeanCurrentlyInCreationException

@Component
public class A {
    private B b;
    @Autowired
    public A(B b) {
        this.b = b;
    }
}
@Component
public class B {
    private A a;
    @Autowired
    public B(A a) {
        this.a = a;
    }
}

解决方法之一:可以使用lazy注解,延迟加载依赖

@Component
public class A {
    private B b;
    @Autowired
    @Lazy
    public A(B b) {
        this.b = b;
    }
}
@Component
public class B {
    private A a;
    @Autowired
    @Lazy
    public B(A a) {
        this.a = a;
    }
}

三、循环依赖形成条件(@Aysnc注解的bean生成了对象的代理)

从日志中可以看到是 tPartnerOrgService 这个bean出现了循环依赖

我在tPartnerOrgService 中使用了@Aysnc注解 进行异步处理,而@Aysnc注解的bean生成了对象的代理,导致Spring bean最终加载的不是一个原始对象导致了此次问题的发生。

解决方案1:给 tPartnerOrgService 加上@Lazy注解

解决方案2:代码优化,不要让@Async的Bean参与循环依赖

四、针对以上问题对Spring如何解决循环依赖进行详细阐述

首先Spring维护了三个Map,也就是我们通常说的三级缓存

  • singletonObjects:俗称单例池,缓存创建完成的单例
  • BeansingletonFactories:映射创建Bean的原始工厂
  • earlySingletonObjects:映射Bean的早期引用,也就是说这个Map里的Bean不是完整的,只是完成了实例化,但还没有初始化

Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。

紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

简单一句话说:先去缓存里找Bean,没有则实例化当前的Bean放到Map,如果有需要依赖当前Bean的,就能从Map取到。

针对上面的@Aysnc注解产生的循环依赖进行分析:

有@Aysnc注解的bean最后生成了一个代理对象,我们结合Spring bean创建的流程来分析这次问题。

  • beanA开始初始化,beanA实例化完成后给beanA的依赖属性beanB进行赋值
  • beanB开始初始化,beanB实例化完成后给beanB的依赖属性beanA进行赋值
  • 因为beanA是支持循环依赖的,所以可以在earlySingletonObjects中可以拿到beanA的早期引用的,但是因为beanB打了@Aysnc注解并不能在earlySingletonObjects中可以拿到早期引用
  • 接下来执行执行initializeBean(Object existingBean, String beanName)方法,这里beanA可以正常实例化完成,但是因为beanB打了@Aysnc注解,所以向Spring IOC容器中增加了一个代理对象,也就是说beanAbeanB并不是一个原始对象,而是一个代理对象
  • 接下来进行执行doCreateBean方法时对进行检测,以下代码有所删减,只保留核心逻辑代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					// 重点在这里,这里会遍历所有依赖的bean,如果beanA依赖beanB和缓存中的beanB不相等
					// 也就是说beanA本来依赖的是一个原始对象beanB,但是这个时候发现beanB是一个代理对象,就会增加到actualDependentBeans
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					// 发现actualDependentBeans不为空,就发生了我们最开始截图的错误
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] 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 " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}
		return exposedObject;
	}

到此这篇关于Springboot详细讲解循环依赖的文章就介绍到这了,更多相关Springboot循环依赖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 关于spring循环依赖问题及解决方案

    目录 一.三种循环依赖的情况 比如几个Bean之间的互相引用 甚至自己“循环”依赖自己 二.解决方案 如何获取依赖 三.解决循环依赖必须要三级缓存吗 结论 四.无法解决的循环依赖问题 1.在主bean中通过构造函数注入所依赖的bean 2.总结 一.三种循环依赖的情况 ①构造器的循环依赖: 这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException异常. ②单例模式下的setter循环依赖: 通过“三级缓存”处理循环依赖,能处理. ③非单例循环依赖

  • SpringBoot2.6.x升级后循环依赖及Swagger无法使用问题

    最近想体验下最新版本的SpringBoot,逛了下官网,发现SpringBoot目前最新版本已经是2.6.4了,版本更新确实够快的.之前的项目升级了2.6.4版本后发现有好多坑,不仅有循环依赖的问题,连Swagger都没法用了!今天给大家分享下升级过程,填一填这些坑! SpringBoot实战电商项目mall(50k+star)地址:https://github.com/macrozheng/mall 聊聊SpringBoot版本 首先我们来聊聊SpringBoot的版本,目前最新版本是2.6.

  • Spring Bean创建和循环依赖

    目录 1 前言 2 Bean 的创建 createBeanInstance populateBean initializeBean 循环依赖问题 总结 1 前言 前文已经讲述了Spring BeanFactory 与 FactoryBean 的区别详情,在本文中将继续讲解 Bean 的创建和初始化,在这个环节中将会涉及到 Bean 的创建.初始化和循环依赖内容. 2 Bean 的创建 在前文中已经讲述了 Spring 容器启动的核心方法 refresh,关于 Bean 的创建和初始化方法都是在 

  • Java中的Spring 如何处理循环依赖

    目录 前言 什么是循环依赖 构造器循环依赖 Setter循环依赖 构造器循环依赖处理 那么Spring到底是如何做的呢? DefaultSingletonBeanRegistry#getSingleton AbstractAutowireCapableBeanFactory#autowireConstructor setter循环依赖处理 AbstractAutowireCapableBeanFactory#doCreateBean prototype模式的循环依赖 总结 前言 Spring如何

  • 关于SpringBoot禁止循环依赖解说

    前言: Spring的Bean管理,一直是整个体系中津津乐道的东西.尤其是Bean的循环依赖,更是很多面试官最喜欢考察的2B知识点之一. 但事实上,项目中存在Bean的循环依赖,是代码质量低下的表现.多数人寄希望于框架层来给擦屁股,造成了整个代码的设计越来越糟,最后用一些奇技淫巧来填补犯下的错误. 还好,SpringBoot终于受不了这种滥用,默认把循环依赖给禁用了! 从2.6版本开始,如果你的项目里还存在循环依赖,SpringBoot将拒绝启动! 验证代码小片段: 为了验证这个功能,我们只需要

  • Springboot详细讲解循环依赖

    目录 一.循环依赖 二.循环依赖形成条件(使用构造器注入) 三.循环依赖形成条件(@Aysnc注解的bean生成了对象的代理) 四.针对以上问题对Spring如何解决循环依赖进行详细阐述 一.循环依赖 顾名思义多个类中的依赖形成了环路,形成了类似于死锁的情况,导致springboot在启动时无法为我们创建Bean.通俗来说 就是beanA中依赖了beanB,beanB中也依赖了beanA. spring是支持循环依赖的,但是默认只支持单例的循环依赖,如果bean中依赖了原型bean,则需要加上l

  • Spring详细讲解循环依赖是什么

    目录 前言 什么是循环依赖 Spring如何处理的循环依赖 只用一级缓存会存在什么问题 只用二级缓存会存在什么问题 Spring 为什么不用二级缓存来解决循环依赖问题 总结 前言 Spring在我们实际开发过程中真的太重要了,当你在公司做架构升级.沉淀工具等都会多多少少用到Spring,本人也一样,在研习了好几遍Spring源码之后,产生一系列的问题, 也从网上翻阅了各种资料,最近说服了自己,觉得还是得整理一下,有兴趣的朋友可以一起讨论沟通一波.回到标题,我们要知道以下几点: (1)什么是循环依

  • SpringBoot详细讲解静态资源导入的实现

    目录 SpringBootWeb开发 1. 静态资源导入 webjars 使用自己的静态资源 总结 2.制作特殊的首页 图标转变 Thymeleaf模板引擎 引入Thymeleaf 取值 有无转义 循环 SpringBootWeb开发 回顾一下: springboot帮助我们配置了什么,能不能进行修改,能修改哪些,能否扩展? xxxAutoConfiguration:向容器中自动配置组件 xxxProperties:自动配置类,装配配置文件中自定义的一些内容 开发要解决的问题: 导入静态资源 首

  • SpringBoot详细讲解通过自定义classloader加密保护class文件

    目录 背景 maven插件加密 注意事项 自定义classloader 隐藏classloader 被保护class手动加壳 总结 背景 最近针对公司框架进行关键业务代码进行加密处理,防止通过jd-gui等反编译工具能够轻松还原工程代码,相关混淆方案配置使用比较复杂且针对springboot项目问题较多,所以针对class文件加密再通过自定义的classloder进行解密加载,此方案并不是绝对安全,只是加大反编译的困难程度,防君子不防小人,整体加密保护流程图如下图所示 maven插件加密 使用自

  • springBoot详细讲解使用mybaties案例

    首先创建springBoot项目,jdk选择1.8 然后倒入mybaties的相关依赖 我们用的springBoot,当然spring全家桶里面含有mybaties,所以我们直接使用升级版的mybaties-plus. 引入这3个 lombok省的我每次创建对象,都需要get.set方法,以及toString (IDEA里面也要安装lombok插件->file->setting->plugin->搜索lombok安装,完后重启idea,这样lombok在idea中不报错) myba

  • SpringBoot详细讲解如何创建及刷新Spring容器bean

    目录 一.前期准备 1.1 创建工程 1.2 创建Controller 二.探究过程 2.1 启动类 2.2 SpringApplication 2.3 ApplicationContextFactory 2.4 SpringApplication 2.5 结论 参考视频:https://www.bilibili.com/video/BV1Bq4y1Q7GZ?p=6 通过视频的学习和自身的理解整理出的笔记. 一.前期准备 1.1 创建工程 创建springboot项目,springboot版本为

  • Springboot详细讲解RocketMQ实现顺序消息的发送与消费流程

    目录 一.创建Springboot项目添加rockermq依赖 二.配置rocketmq 三.新建一个controller来做消息发送 四.创建消费端监听消息消费消息 五.启动服务测试顺序消息发送与消费 如何实现顺序消息? 需要程序保证发送和消费的是同一个 Queue rocketmq默认发送的消息是进入多个消息队列,然后消费端多线程并发消费,所以默认情况,不是順序消费消息的:有時候,我们需要顺序消费一批消息,比如电商系统 订单创建.支付.完成操作,需要順序执行: RocketMQTemplat

  • SpringBoot详细讲解yaml配置文件的用法

    目录 1.基本语法 2.数据类型 3.代码测试 4.开启补全提示 1.基本语法 key: value:kv之间有空格 大小写敏感 使用缩进表示层级关系 缩进不允许使用tab,只允许空格 缩进的空格数不重要,只要相同层级的元素左对齐即可 '#'表示注释 字符串无需加引号,如果要加,单引号’'.双引号""表示字符串内容会被 转义.不转义 2.数据类型 1.字面量:单个的.不可再分的值.date.boolean.string.number.null k: v 2.对象:键值对的集合.map.

  • SpringBoot详细讲解日志文件

    目录 1 日志的功能是什么? 2 如何自定义日志打印 2.1 在程序中获取日志对象 2.2 调用日志对象打印日志 2.3 查看日志打印的结果 3 日志的级别 3.1 日志级别的分类 3.2 日志级别的设置 4 日志持久化 5 更简单的日志输出 5.1 添加 lombok 依赖 5.2 输出日志 5.3 lombok 更多的注解 1 日志的功能是什么? 如果程序报错了, 却不能从控制台查看日志, 那么就不知道错误的原因了. 日志的功能 : 快速的排查和定位问题 记录用户登录的日志 记录系统的操作日

  • SpringBoot详细讲解视图整合引擎thymeleaf

    目录 1. 支持的视图技术 2. Thymeleaf 2.1 Thymeleaf语法 2.2 标准表达式 1. 变量表达式 ${…} 2. 选择变量表达式 *{…} 3. 消息表达式 #{…} 4. 链接表达式 @{…} 5. 片段表达式 ~{…} 3. 基本使用 3.1 Thymeleaf模板基本配置 3.2 静态资源的访问 3.3 完成数据的页面展示 1. 创建Spring Boot项目 2. 编写配置文件 3. 创建web控制类 4. 创建模板页面并引入静态资源文件 5.效果测试 1. 支

随机推荐