Spring boot启动流程之解决循环依赖的方法

 更新时间:2024年02月06日 12:05:47   作者:后仰大风车  
循环依赖,指的是两个bean之间相互依赖,形成了一个循环,spring解决循环依赖的方式是在bean的实例化完成之后,所以不要在构造方法中引入循环依赖,因为这时对象还没有实例化,spring也无法解决,本文给大家介绍Spring boot循环依赖的解决方法,一起看看吧

循环依赖,指的是两个bean之间相互依赖,形成了一个循环。
目前使用的spring版本中,在启动时默认关闭了循环依赖。假设代码中两个bean相互使用@Autowired注解进行自动装配,启动时会报错如下:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
翻译过来就是:
不鼓励使用循环引用,默认情况下禁止使用循环引用。更新应用程序以删除bean之间的依赖循环。作为最后的手段,可以通过设置spring.main.allow-circular-references为true自动打破循环。

spring还是支持循环依赖的,但前提是我们要手动将其打开。我们在配置文件中设置属性 spring.main.allow-circular-references = true。

解决循环依赖方法:

1、在bean A实例化完成后,如果检测到支持循环依赖,会先把A缓存起来

// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		//判断当前bean为单例,且允许循环依赖,且该bean正在创建中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

addSingletonFactory方法的第二个参数,传入的是一个实现了ObjectFactory<?>函数式接口的lambda表达式,表达式中调用的是getEarlyBeanReference方法,获取bean的早期引用。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			/*
			如果 singletonObjects 中不存在当前bean(当前bean还在创建,还没生成最终的单例对象,显然成立),则:
			1)把当前的singletonFactory放入singletonFactories变量中(把获取早期引用的lambda表达式放进三级缓存);
			2)把当前bean从earlySingletonObjects变量中移除(清理二级缓存,确保在循环引用触发时才生成早期引用;实际这里本来也没有);
			3)把当前beanName放入registeredSingletons变量中。
			*/
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

这里可能还看不出来什么头绪,继续往下走,在bean A初始化的时候,会自动装配依赖的bean B、C等,而在B、C初始化时又会自动装配它们所依赖的A(当然B、C在实例化之后也会和A一样先缓存起来),但这个时候A也正在创建中,最终的对象肯定是拿不到的,这时候就考虑生成一个A的早期引用,先提供给B、C。

2、在doGetBean方法获取bean A的时候,会先调用getSingleton方法查找缓存,部分源码如下:

// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

接下来看getSingleton方法:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		//先从singletonObjects中获取bean A,之前说过显然获取不到
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			//再从earlySingletonObjects中获取bean,刚开始也是没有的
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							//最后从singletonFactories里拿到singletonFactory
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								//调用getObject方法获取早期引用,放进earlySingletonObjects,清理singletonFactories
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

到这里三级缓存的结构就比较清晰了:

一级缓存:singletonObject,这里存放的是最终初始化完成的bean对象。
也就是说bean初始化完成后 ,下次再拿的时候,可以直接从缓存里找,所以这是一个通用缓存,并非专门为循环依赖而设计。

二级缓存:earlySingletonObjects,存放完成了实例化,但还没有完成初始化的半成品bean对象,其实就是相当于把不完整的对象提前暴露了出来。
比如当bean A在初始化注入bean B、C的时候,B、C的初始化又都依赖了A,这时候从一级缓存里找A肯定是找不到的,于是就会下探到二级缓存,如果拿到了A,doGetBean方法就会返回而不是又进入A的初始化,相当于这个循环被切断了。然后B、C就能够完成初始化,最终A也能完成初始化。

三级缓存:singletonFactories,对象工厂,存放的是获取半成品bean对象的方法实现。
二级缓存的数据并不是凭空产生的,而是由于一开始访问二级缓存找不到数据,然后下探到三级缓存,调用了getObject方法,拿到半成品对象后才放进去的,然后其他bean才可以直接从二级缓存里面取。
二级缓存放入数据后,三级缓存对应的数据不再需要,立即被移除。

singletonFactory.getObject()方法其实就是lambda表达式的实现,调用了getEarlyBeanReference方法:

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;
	}

这里调用 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference方法,获取了bean实例的早期引用,其实就是还没有完成初始化的半成品bean对象本身(也可能是创建了一个它的代理对象)。

能不能不要三级缓存,每次实例化bean之后,直接生成一个早期引用放到二级缓存中,方便循环依赖?
可以但不合理,早期引用是可以用来解决循环依赖问题,但实际上spring默认是不推荐使用循环依赖的,如果不结合实际情况是否需要循环依赖,直接缓存一个对象,这种设计显然有问题。

能不能不要二级缓存,每次出现循环依赖的时候,直接调用三级缓存的方法获取早期引用?
不行,首先是重复调用,有可能每次获取早期引用的工作都是完全重复的;其实如果是一个需要AOP动态代理的bean对象,每次都会产生一个新的代理对象,不符合单例的设计原则。

还有个细节,就是当一个bean初始化完成,拿到单例对象之后,会调用addSingleton方法:

protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, singletonObject);
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

这里面不仅将对象放进了一级缓存singletonObjects,还同时清理了对应的二、三级缓存。

最后要注意的是,spring解决循环依赖的方式是在bean的实例化完成之后,所以不要在构造方法中引入循环依赖,因为这时对象还没有实例化,spring也无法解决。

到此这篇关于Spring boot启动流程-解决循环依赖的文章就介绍到这了,更多相关Spring boot循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JDK12的新特性之teeing collectors

    JDK12的新特性之teeing collectors

    这篇文章主要介绍了JDK12的新特性之teeing collectors的相关资料,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • SpringBoot中多环境配置和@Profile注解示例详解

    SpringBoot中多环境配置和@Profile注解示例详解

    这篇文章主要介绍了SpringBoot中多环境配置和@Profile注解,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-01-01
  • java中读取配置文件中数据的具体方法

    java中读取配置文件中数据的具体方法

    java中读取配置文件中数据的具体方法,需要的朋友可以参考一下
    2013-06-06
  • gRPC与SpringBoot整合思路和步骤

    gRPC与SpringBoot整合思路和步骤

    在现代微服务架构中,gRPC已经成为了非常受欢迎的通信协议,与SpringBoot整合,它为开发者提供了简洁、高效构建分布式应用,在整合gRPC与SpringBoot时,将gRPC的服务端和客户端分别封装到SpringBoot的应用中,感兴趣的朋友一起看看吧
    2023-08-08
  • springboot中手动提交事务的实现方法

    springboot中手动提交事务的实现方法

    手动提交事务可以提供更灵活的控制,以便在分布式环境中处理事务的提交和回滚,本文就来介绍一下springboot中手动提交事务的实现方法,感兴趣的可以了解一下
    2024-01-01
  • 详解Java的线程状态

    详解Java的线程状态

    本文主要为大家详细介绍一下Java的线程状态,文中的示例代码讲解详细,对我们学习有一定的帮助,感兴趣的小伙伴可以跟随小编学习一下
    2022-11-11
  • Hibernate核心思想与接口简介

    Hibernate核心思想与接口简介

    这篇文章主要介绍了Hibernate核心思想与接口的相关内容,需要的朋友可以参考下。
    2017-09-09
  • 详解Java中final的用法

    详解Java中final的用法

    本文主要介绍了Java中final的使用方法,final是java的关键字,本文就详细说明一下它的使用方法,需要的朋友可以参考下
    2015-08-08
  • 详解Java编写算法时如何加快读写数据速度

    详解Java编写算法时如何加快读写数据速度

    这篇文章主要为大家详细介绍了Java在编写算法时如何加快读写数据速度,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • 解决maven中只有Lifecycle而Dependencies和Plugins消失的问题

    解决maven中只有Lifecycle而Dependencies和Plugins消失的问题

    这篇文章主要介绍了maven中只有Lifecycle而Dependencies和Plugins消失的问题及解决方法,本文通过图文的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-07-07

最新评论