当前位置: 首页 > article >正文

Spring循环依赖

前言

不知道大家听过Spring的循环依赖这个问题吗,而且这个问题是面试经常问的,属于Spring的一个比较重要的话题,也比较典型,比较考验一个人对Spring的研究程度

循环依赖问题,本文会通过三个方面来简单介绍

1、什么是Spring的循环依赖

2、多种情况下的循环依赖

3、Spring如何解决循环依赖

了解Spring循环依赖

什么是循环依赖

        

@Component
public class AService {
    // AService中注入了BService
 @Autowired
 private BService bService;
}

@Component
public class BService {
    // BService中也注入了AService
 @Autowired
 private AService aService;
}

这是属于比较常见的一种循环依赖,还有就是更多的之间的相互依赖,比如A依赖B,B依赖C,C依赖A,类似于三角恋…

当然也有特殊的,自己的依赖自己

// 自己依赖自己
@Component
public class AService {
    // A中注入了A
 @Autowired
 private AService aService;
}

关于上面service的Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化

我们如果用一个常人的思维去考虑,这肯定是做不到的啊,A需要B,B需要A,这不成死循环了,和死锁一个道理了,这就很尴尬了,该怎么解决呢,接着看下去

多种情况下的循环依赖

Spring的循环依赖也是可能出现多种情况的,比如构造器注入,setter注入等等,那什么情况下Spring可以解决循环依赖,什么情况下又不能解决呢

Spring解决循环依赖的前提条件就是:

1、出现循环依赖的bean必须是单例的

2、依赖注入的方式不能全是构造器注入

注意不能全是这几个字眼,这里需要强调一点的是,大家可能会看到很多关于Spring解决循环依赖的博客,其中只能解决setter注入的方式这种说法是错误的,只要不全是构造器注入Spring就可以解决

Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化,在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过后置处理器设置,知道了这一点,可以更好的帮助大家去理解

上面说的第一点必须是单例的其实很好理解,你想啊,如果是多个实例,该引入哪个实例就不知道了,Spring框架就蒙圈了(spring框架默认单例)

但是第二点呢,不能全是构造器注入呢,先看代码

@Component
public class AService {
// @Autowired
// private BService bService;
 public AService(BService bService) {

 }
}

@Component
public class BService {

// @Autowired
// private AService aService;

 public BService(AService aService){

 }
}

上面这个例子大家看得懂吧,别告诉我你看不懂,AService中的BService注入是通过构造器,反之也是通过构造器注入,这个时候Spring是无法解决循环依赖问题的,如果项目中出现两个这样的引用,在启动的时候就会直接抛出异常

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

解决循环依赖

首先呢,Spring解决循环依赖依靠的就是内部维护的三个Map,也就是咱们常说的三级缓存,不知道大家听过没有

	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

 之所以被叫做三级缓存,大概是因为注释上都是用Cache,而起的作用也类似一个缓存的作用

1、singletonObjects:存放已经经历了完整生命周期的Bean对象 成员属性都是有值的

2、earlySingletonObjects:存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)

3、singletonFactories:存放可以生成Bean的工厂

 二缓和三缓属于消耗的过程, 为了创造完整的单例bean, 暂时缓存在这里, 过后就清除掉了(不同于常规缓存理解, 一般有更新时候, 各级缓存同步, 这里是删除)

1、A找B找不到:

创建AService的过程中发现需要BService,于是AService将去寻找BService,发现找不到,寻找的路径是一级缓存、二级缓存、三级缓存,于是AService把自己放到了三级缓存中

2、B找A找到了:

实例化BService,实例化过程中发现需要AService,于是也是按照上述路径去寻找,在三级缓存中找到了AService

3、B创建完成:

然后就把三级缓存中的AService拿出来,放到了二级缓存中,并删除三级缓存中的AService,此时BService可以成功引用,顺利初始化完毕,把自己放到了一级缓存中了(而此时BService中的AService依然是创建中的状态

4、A创建完成:

继续完善AService,此时再去寻找BService,拿出来直接引用就好了,把自己放入到一级缓存中,删除二级缓存,删除在创建的状态信息


来一起简单的分析一波源码,不看太多,不用发愁

1、先将AService封装成ObjectFactory, 存入三级缓存中

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

 执行Lambda 执行该方法:getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

 将getEarlyBeanReference生成的结果存入三级缓存

	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

2、AService中属性赋值,先实例化BService, BService又依赖AService

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
       //根据beanName 从单例池中获取bean对象
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
                //从二级缓存中获取bean对象
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
                    // 从三级缓存中获取singletonFactory
					ObjectFactory<?> singletonFactory = this.singletonFactories
                        .get(beanName);
					if (singletonFactory != null) {
                        ##执行三级缓存对应的..执行Lambda  执行该方法:
                            getEarlyBeanReference
                        // 判断如果开启了aop 则会生成代理类 AService代理类
                        // 如果没有开启aop的情况下 则直接返回原始不完整bean对象
						singletonObject = singletonFactory.getObject();
                        //将该代理类存入到二级缓存中 存入不完整对象
						this.earlySingletonObjects.put(beanName, singletonObject);
						//在删除三级缓存
                        this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

说明: 可以看到, 在获取Aservice过程中, 依次从一二三级缓存中获取, 执行三级缓存的lumuda表达式, 将生成的bean存入二缓, 并删除三缓, Bservice从二缓找到Aservice, 实例化完成, 存入一缓

执行lumuda表达式的实际作用是为了判断返回的具体哪种对象

  • 如果我们开启了aop的情况下 则返回aop代理对象
	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}
  • 如果我们没有开启aop的情况下 则返回原始对象

@Override
	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

spring已经帮我们解决了循环依赖问题, 在实际开发中 注意写代码规范, 避免出现

1. 自己依赖自己

2. 开启了多例模式

3. AB同时使用构造注入

在排除以上情况,  如果di属性注入出现循环依赖, 使用@lazy解决即可


http://www.kler.cn/a/7889.html

相关文章:

  • Go 语言中,golang结合 PostgreSQL 、MySQL驱动 开启数据库事务
  • ESLint 使用教程(五):ESLint 和 Prettier 的结合使用与冲突解决
  • 微信小程序的主体文件和页面文件介绍
  • WordPress 6.7 “Rollins”发布
  • 认识一下Unicorn
  • 2024 年 Apifox 和 Postman 对比介绍详细版
  • LCP 44. 开幕式焰火
  • Chat-GLM 详细部署(GPU显存>=12GB)
  • PMP如何备考,才能一次通过呢?
  • Twitter-高性能hash_map
  • AM62X +FPGA+AD/DA 的TSN工业网关解决方案
  • vue实现轨迹回放(很详细)
  • 最近写的怎样审核不通过
  • Android如何做出带有复杂水印的图片
  • Web基础与HTTP协议
  • Maven项目中的依赖出现版本冲突,最终发现是对Dependency Scope理解有误
  • Win11的两个实用技巧系列之找不到wifi网络的解决方法、双系统开机选择系统方法
  • 数据库系统工程师——考试分析(2023备考)
  • 【虚幻引擎】UE4 动画蓝图,动画,状态机三者之间的联系
  • UTONMOS:2023年,亚洲或将实现区块链游戏复兴
  • 了解Mysql
  • 【区块链技术开发】基于Web3.js以太坊网络上的智能合约的交互及其应用
  • GP03丨宽窄基资金管理增强策略
  • PyQt5可视化 7 饼图和柱状图实操案例 ③柱状图的实现【超详解】
  • java-replace into详解(SQL)
  • 面试Interview