一文了解Spring依赖注入时循环依赖问题

news/2024/5/20 19:22:40

目录

什么是循环依赖

凡是Java的循环依赖都会有问题?

为什么Spring循环依赖会有问题?

Spring解决循环依赖问题的思路?

设置二级缓存对象池

方案一直接将实例化对象放入早期对象池

方案一缺点

方案二-将实例化对象处理AOP后放入早期对象池

方案二-缺点

设置三级缓存对象池

Spring解决循环依赖源码解析


什么是循环依赖

对象A依赖对象B,对象B依赖对象A,这是一种常见且正常的依赖关系。

Java原生创建对象循环依赖不会有问题

通过Java直接创建对象,并不会产生问题;因为Java创建的只需要实例化后就是一个完整的对象,循环依赖时,只需将所有的对象都先实例化,再处理依赖关系。

publc class A{public B b;
}
publc class B{public A a;
}
public class Test{public static void main(String[] args){A a = new A();B b = new B();a.b = b;b.a = a;}
}

为什么Spring循环依赖会有问题?

spring创建的bean对象,不只是简单的实例化,还要经历属性注入,初始化前,初始化,初始化后等过程,只有经历完这个过程,才是一个完整的bean。要了解spring生命周期,可以参考这篇博文:

一句话解释:处理依赖关系时,由于bean没有经历完完整的生命周期,还不是一个完整的bean,导致有循环依赖关系的bean都陷入等待对方完成状态,而无法继续向后执行。请参考下面的流程图感受下:

ABean创建-->依赖了B属性-->触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程中)

Spring解决循环依赖问题的思路

设置二级缓存对象池

方案一直接将实例化对象放入早期对象池

最简单的思路就是在类似于普通对象创建的方式,在实例化后有一个原始的对象池,在判断单例池中是否有bean对象时,若无则从原始对象池中获取,可以解决上诉问题


方案一缺点

如果A的原始对象注入给B的属性之后,在初始化后阶段A的原始对象进行了AOP产生了一个代理对象,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突

方案二-将实例化对象处理AOP后放入早期对象池

放入早期对象池前,对实例化后对象A处理,若有AOP则放入代理对象A,若无AOP,则放入原始对象,如下图所示

方案二-缺点

但是在实例化后,直接处理对象A将代理对象或原始对象放入对象原始池这种方式,对所有对象的创建过程都起作用,而没有循环依赖的对象创建过程,无需将AOP过程前置,所以这个时机不合适

设置三级缓存对象池

Spring为了解决非循环依赖的对象不进行AOP前置,使采用函数式编程方式,在实例化后,先缓存一个对象工厂函数,此时不执行内部逻辑,待从原始对象池中获取对象时,才真正获取到对象工厂去调用函数获取早期对象,存放到二级缓存对象池中即早期对象池

三级缓存对象池的作用

1.单例对象池(singletonObjects中):缓存的是经历过完整生命周期后对象

2.早期对象池(earlySingletonObjects):缓存的是没有经历过完整生命周期的对象,实例化后原始对象或者实例化后的代理对象

3.对象工厂(singletonFactories):缓存的是对象工厂,对象工厂中定义了创建早期对象的函数

三者的创建时机,获取时机如下流程图:

Spring解决循环依赖源码解析

查看获取属性对象时源码也可知:获取对象时先从单例池中获取对象,获取不到从早期对象池中获取对象,若仍获取不到则从对象工厂中获取对象bean的对象工厂,创建早期对象,存入早期对象池中,并且该对象工厂从对象工厂池中删除。

@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}

对象工厂调用getObject()时,实际调用的就是

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

会遍历所有实现了SmartInstantiationAwareBeanPostProcessor的类,调用getEarlyBeanReference获取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;}

SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference会直接获取到原始bean

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {/*** Obtain a reference for early access to the specified bean,* typically for the purpose of resolving a circular reference.* <p>This callback gives post-processors a chance to expose a wrapper* early - that is, before the target bean instance is fully initialized.* The exposed object should be equivalent to the what* {@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization}* would expose otherwise. Note that the object returned by this method will* be used as bean reference unless the post-processor returns a different* wrapper from said post-process callbacks. In other words: Those post-process* callbacks may either eventually expose the same reference or alternatively* return the raw bean instance from those subsequent callbacks (if the wrapper* for the affected bean has been built for a call to this method already,* it will be exposes as final bean reference by default).* <p>The default implementation returns the given {@code bean} as-is.* @param bean the raw bean instance* @param beanName the name of the bean* @return the object to expose as bean reference* (typically with the passed-in bean instance as default)* @throws org.springframework.beans.BeansException in case of errors*/default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {return bean;}}

但是spring提供了AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor,重写了getEarlyBeanReference方法,若开启了代理,那会直接执行AbstractAutoProxyCreator的getEarlyBeanReference获取代理后的对象

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware{
// 提前进行AOP@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);}
}


https://www.xjx100.cn/news/3091922.html

相关文章

常用的指令集

常用的命令行指令 以下是一些常用的命令行指令&#xff0c;它们的类型和作用&#xff1a; 命令类型作用ls文件/目录管理列出当前目录下的文件和子目录cd文件/目录管理改变当前工作目录mkdir文件/目录管理创建新目录rm文件/目录管理删除文件或目录cp文件/目录管理复制文件或目…

python连接hive报错:TypeError: can‘t concat str to bytes

目录 一、完整报错 二、解决 三、 其他报错 四、impala方式连接hive 或者直接使用 pip install pyhive[hive] 安装。需要先 pip uninstall pyhive。 一、完整报错 Traceback (most recent call last): File "D:/Gitlab/my_world/hive2csv.py", line 18, in <…

ubuntu中经常脚本汇总

ubuntu中经常脚本汇总 # get pascal voc # standard voc mkdir -p data cd data wget -c http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar tar -xvf VOCtrainval_11-May-2012.tar wget -c http://pjreddie.com/media/files/VOC2012test.tar tar -…

如何使用 RTLS?

RTLS 的不同应用几乎是无限的。毕竟&#xff0c;几乎任何人都可以从更好地了解事物的实时变化中受益。位置数据的一般价值导致了各种各样的最终用途应用&#xff0c;从制造工作跟踪、库存管理、堆场管理、供应链和物流&#xff0c;到医疗保健、动物跟踪以及采矿和采矿业人员的安…

ES索引数据清理脚本示例

说明&#xff1a;我得索引是按月份创建的&#xff0c;索引名后面都有yyyy.MM 需求&#xff1a;删除三个月以前的索引&#xff0c;清理收集的应用日志数据&#xff0c;释放磁盘空间 #!/bin/bash# 定义 Elasticsearch 节点的地址 ELASTICSEARCH_HOST"192.168.53.100" …

开发盲盒商城的意义

开发盲盒商城的意义在于为电商行业带来新的增长机会&#xff0c;满足消费者对购物方式趣味性的需求&#xff0c;同时提升用户的参与度&#xff0c;为商家带来更多销售机会和增强影响力的机遇。 盲盒商城系统通过独特的盲盒玩法&#xff0c;为用户带来了全新的趣味购物体验&…

【图像分类】【深度学习】【Pytorch版本】Inception-ResNet模型算法详解

【图像分类】【深度学习】【Pytorch版本】Inception-ResNet模型算法详解 文章目录 【图像分类】【深度学习】【Pytorch版本】Inception-ResNet模型算法详解前言Inception-ResNet讲解Inception-ResNet-V1Inception-ResNet-V2残差模块的缩放(Scaling of the Residuals)Inception-…

ueditor整合到thinkPHP里

<?phpnamespace app\ueditor\controller;use think\Controller;class Ueditor extends Controller {//首页public function upload(){//header(Access-Control-Allow-Origin: http://www.baidu.com); //设置http://www.baidu.com允许跨域访问//header(Access-Control-Allow…