IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Springboot 循环依赖 -> 正文阅读

[Java知识库]Springboot 循环依赖

一、循环依赖

顾名思义多个类中的依赖形成了环路,形成了类似于死锁的情况,导致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:俗称单例池,缓存创建完成的单例Bean
  • singletonFactories:映射创建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;
	}

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-18 23:18:09  更:2022-06-18 23:18:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:48:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码