循环依赖源码深度解析

 

singletonObjects (一级缓存)它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。
earlySingletonObjects(二级缓存)映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance.
singletonFactories(三级缓存) 映射创建Bean的原始工厂
 

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况:

spring初始化bean的过程如下:
1>首先尝试从一级缓存中获取serviceA实例,发现不存在并且serviceA不在创建过程中;
2>serviceA完成了初始化的第一步(实例化:调用createBeanInstance方法,即调用默认构造方法实例化);
3>将自己(serviceA)提前曝光到singletonFactories中;
4>此时进行初始化的第二步(注入属性serviceB),发现自己依赖对象serviceB,此时就尝试去get(B),发现B还没有被实create,所以走create流程;
5>serviceB完成了初始化的第一步(实例化:调用createBeanInstance方法,即调用默认构造方法实例化);
6>将自己(serviceB)提前曝光到singletonFactories中;
7>此时进行初始化的第二步(注入属性serviceA);
8>于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀);
9>B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中;
10>此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中;

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

4、循环依赖的N种场景

这里主要讲解spring是如何解决单例setter注入时的循环依赖问题,其他四种循环依赖场景后文再一一分析讲解

3.1、单例的setter注入
这种注入方式应该是spring用的最多的,代码如下:

@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}@Service
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}


spring内部有三级缓存:

singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
下面用一张图告诉你,spring是如何解决循环依赖的:

上面(3、Spring怎么解决循环依赖)中已经讲解过spring解决单例循环依赖的过程,这里细心的朋友可能会发现在这种场景中第二级缓存(earlySingletonObjects)作用不大。
那么问题来了,为什么要用第二级缓存呢?
试想一下,如果出现以下这种情况,我们要如何处理?

@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB;@Autowiredprivate ServiceB serviceC;
}@Service
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}@Service
public class ServiceC {@Autowiredprivate ServiceA serviceA;
}


serviceA依赖于serviceB和serviceC,而serviceB依赖于serviceA,同时serviceC也依赖于serviceA。按照上图的流程可以把serviceA注入到serviceB,并且sServiceA的实例是从第三级缓存中获取的。假设不用第二级缓存,serviceA注入到serviceC的流程如图:
图2

 

serviceA注入到serviceC又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。这样不是有问题?

为了解决这个问题,spring引入的第二级缓存。上面图1其实serviceA对象的实例已经被添加到第二级缓存中了,而在serviceA注入到TestService3时,只用从第二级缓存中获取该对象即可。
图3

还有个问题,第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?
答:不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。
针对这种场景spring是怎么做的呢?
答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isDebugEnabled()) {
        logger.debug("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。

3.2、多例的setter注入
这种注入方法偶然会有,具体代码如下:

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}


可能你会认为这种情况spring容器启动会报错,其实是不会,为什么呢?其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了beanFactory.preInstantiateSingletons()方法:

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();this.configureHeadlessProperty();SpringApplicationRunListeners listeners = this.getRunListeners(args);listeners.starting();Collection exceptionReporters;try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);Banner printedBanner = this.printBanner(environment);context = this.createApplicationContext();exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);this.refreshContext(context);this.afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}listeners.started(context);this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}try {listeners.running(context);return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}}

 

refresh:

public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {this.prepareRefresh();ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);this.initMessageSource();this.initApplicationEventMulticaster();this.onRefresh();this.registerListeners();this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (BeansException var9) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);}this.destroyBeans();this.cancelRefresh(var9);throw var9;} finally {this.resetCommonCaches();}}}

 

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {if (beanFactory.containsBean("conversionService") && beanFactory.isTypeMatch("conversionService", ConversionService.class)) {beanFactory.setConversionService((ConversionService)beanFactory.getBean("conversionService", ConversionService.class));}if (!beanFactory.hasEmbeddedValueResolver()) {beanFactory.addEmbeddedValueResolver((strVal) -> {return this.getEnvironment().resolvePlaceholders(strVal);});}String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);String[] var3 = weaverAwareNames;int var4 = weaverAwareNames.length;for(int var5 = 0; var5 < var4; ++var5) {String weaverAwareName = var3[var5];this.getBean(weaverAwareName);}beanFactory.setTempClassLoader((ClassLoader)null);beanFactory.freezeConfiguration();beanFactory.preInstantiateSingletons();}

 

红框的地方明显能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。如何让它提前初始化bean呢?只需要再定义一个单例的类,在它里面注入serviceA: 

@Service
public class ServiceC {@Autowiredprivate ServiceA serviceA;
}

重新启动程序,执行结果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

果然出现了循环依赖。

注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。

3.3、构造器注入
这种注入方式现在其实用的已经非常少了,但是我们还是有必要了解一下,看看如下代码:

@Service
public class ServiceA {public ServiceA(ServiceB serviceB) {}
}@Service
public class ServiceB {public ServiceB(ServiceA serviceA) {}
}

运行结果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

出现了循环依赖,为什么呢?

 从图中的流程看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。

3.4、单例的代理对象setter注入
这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。

@Service
@EnableAsync
public class ServiceA {@Autowiredprivate ServiceB serviceB;@Asyncpublic void test1() {System.out.println("async");}
}@Service
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}



程序启动会报错,出现了循环依赖:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘serviceA’: Bean with name ‘serviceA’ has been injected into other beans [serviceB] in its raw version as part of a circular reference

为什么会循环依赖呢?答案就在下面这张图中:

 doCreateBean

 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {instanceWrapper = this.createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}synchronized(mbd.postProcessingLock) {if (!mbd.postProcessed) {try {this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);} catch (Throwable var17) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);}mbd.postProcessed = true;}}boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);if (earlySingletonExposure) {if (this.logger.isTraceEnabled()) {this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");}this.addSingletonFactory(beanName, () -> {return this.getEarlyBeanReference(beanName, mbd, bean);});}Object exposedObject = bean;try {this.populateBean(beanName, mbd, instanceWrapper);exposedObject = this.initializeBean(beanName, exposedObject, mbd);} catch (Throwable var18) {if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {throw (BeanCreationException)var18;}throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);}if (earlySingletonExposure) {Object earlySingletonReference = this.getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;} else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {String[] dependentBeans = this.getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);String[] var12 = dependentBeans;int var13 = dependentBeans.length;for(int var14 = 0; var14 < var13; ++var14) {String dependentBean = var12[var14];if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}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.");}}}}try {this.registerDisposableBeanIfNecessary(beanName, bean, mbd);return exposedObject;} catch (BeanDefinitionValidationException var16) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);}}

说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存是否存在, 代理对象和原始对象是否相等。

到此发现第二级缓存存在并且代理对象和原始对象是不相等,因此抛出上面异常。

如果这时候把ServiceA改个名字,改成:ServiceC,其他的都不变。

@Service
public class ServiceB {@Autowiredprivate ServiceC serviceC;
}@Service
@EnableAsync
public class ServiceC {@Autowiredprivate ServiceB serviceB;@Asyncpublic void test1() {System.out.println("async");}
}

 再重新启动一下程序,成功了,没有报错了!!!
这又是为什么?
这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以ServiceA比ServiceB先加载,而改了文件名称之后,ServiceB比ServiceC先加载。为什么ServiceB比ServiceC先加载就没问题呢?答案在下面这张图中

 这种情况ServiceC第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。

3.5、DependsOn循环依赖

@Service
@DependsOn("serviceB")
public class ServiceA {}@Service
@DependsOn("serviceA")
public class ServiceB {}

程序启动之后,执行结果:

Circular depends-on relationship between ‘serviceB’ and ‘serviceA’

这个例子中本来如果ServiceA和ServiceB都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题。

这又是为什么?

答案在AbstractBeanFactory类的doGetBean方法的这段代码中:

初始化bean前,它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常。

5、出现循环依赖如何解决?

项目中如果出现循环依赖问题,说明是spring默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:

5.1、生成代理对象产生的循环依赖
生成代理对象产生的循环依赖这类循环依赖问题解决方法很多,主要有:
1、使用@Lazy注解,延迟加载
2、使用@DependsOn注解,指定加载先后关系
3、修改文件名称,改变循环依赖类的加载顺序

5.2、DependsOn循环依赖
使用@DependsOn产生的循环依赖这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

5.3、多例循环依赖
多例循环依赖这类循环依赖问题可以通过把bean改成单例的解决。

5.3、构造器循环依赖
构造器循环依赖这类循环依赖问题可以通过使用@Lazy注解解决。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/576160.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多线程间共享变量线程安全问题——ThreadLocal

Java并发编程中很重要的类&#xff1a;ThreadLocal 在多线程应用程序中&#xff0c;对共享变量进行读写的场景是很常见的。如果不使用一定的技术或方案&#xff0c;会引发各种线程安全的问题。常见解决线程安全的方式有synchronized、volatile等方式&#xff0c;但synchronized…

java8流式操作

简介&#xff1a;Stream 中文称为 “流”&#xff0c;通过将集合转换为这么一种叫做 “流” 的元素序列&#xff0c;通过声明性方式&#xff0c;能够对集合中的每个元素进行一系列并行或串行的流水线操作。 操作分类&#xff1a; .stream() stream()把一个源数据&#xff0c;可…

ArrayList源码阅读

private static void extracted() {ArrayList<StudentVO> arrayList new ArrayList<StudentVO>();arrayList.add(new StudentVO("张三", 23));arrayList.add(new StudentVO("李四", 24));arrayList.add(new StudentVO("王五", 24))…

常用的JS小功能整理

<a href"#" onclick "this.style.behaviorurl(#default#homepage);this.sethomepage(http://www.mingrisoft.com)" style" color:Black; font-size: 9pt; font-family: 宋体; text-decoration :none;" >设置主页</a> <a href&quo…

类的加载过程

类的加载过程 代码 public class Father{private int i test();private static int j method();static{System.out.print("(1)");}Father(){System.out.print("(2)");}{System.out.print("(3)");)public int test(){System.out.print("(…

教你如何开发一个 SpringBoot starter

从前从前&#xff0c;有个面试官问我一个 SpringBoot Starter 的开发流程&#xff0c;我说我没有写过 starter&#xff0c;然后就没有然后了&#xff0c;面试官说我技术深度不够。 我想说这东西不是很简单吗&#xff0c;如果要自己写一个出来也是分分钟的事情。至于就因为我没…

两分钟彻底让你明白Android Activity生命周期(图文)!

转&#xff1a;http://blog.csdn.net/qyf_5445/article/details/8290232 首先看一下Android api中所提供的Activity生命周期图(不明白的&#xff0c;可以看完整篇文章&#xff0c;在回头看一下这个图&#xff0c;你会明白的): Activity其实是继承了ApplicationContext这个类&am…

spring4和spring5的aop执行顺序区别?

spring4单切面 spring4多切面 spring4 spring5

jquery datepicker 点击日期控件不会自动更新input的值

页面代码&#xff1a;<link href"http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" rel"stylesheet" type"text/css"/> <link href"/static/css/main.css" rel"stylesheet" type"text/css"/…

一个b+树库存放多少索引记录

MySQL中InnoDB页的大小默认是16k。也可以自己进行设置。&#xff08;计算机在存储数据的时候&#xff0c;最小存储单元是扇区&#xff0c;一个扇区的大小是 512 字节&#xff0c;而文件系统&#xff08;例如 XFS/EXT4&#xff09;最小单元是块&#xff0c;一个块的大小是 4KB。…

检索函数retrieve

转载于:https://www.cnblogs.com/flowjacky/archive/2012/12/28/2836729.html

BootCDN——React入门学习

首先下载:react依赖&#xff1a;react.js、react-dom.js、babel.js 这种方式容易出错&#xff0c;所以不使用这个 使用下面方式正真的用法;Babel 中文网 Babel - 下一代 JavaScript 语法的编译器

redis事务命令复习

命令复习&#xff1a; multi&#xff1a;开启事务 开启事务之后&#xff0c;讲要操作的命令都放到了QUEUED&#xff08;queued&#xff09;队列里&#xff0c;然后通过EXEC命令一起提交。 对于WATCH命令&#xff1a; 开启了事务&#xff0c;没有提交&#xff0c;这时候又有一…

STM32示波器 信号发生器

关于stm32的示波器&#xff0c;网上以经有很多了。这里还是想把自己的设计思想发表出来。这个项目已经准备了很久。这里首先要感谢以前的团队&#xff0c;非常感觉陈师和覃总两位经验丰富的嵌入式工程师&#xff0c;获得了不少多方面的考虑。如果不是工作调整等原因&#xff0c…

FlashPaper安装及使用方法

FlashPaper安装及使用方法 一、FlashPaper的安装 第一步&#xff1a;下载FlashPaper2.2安装包 点击下面链接下载FlashPaper2.2 FlashPaper2.2下载 第二步&#xff1a;安装FlashPaper2.2 将zip压缩包解压至磁盘的某一文件夹中&#xff0c;注意&#xff0c;此版本FlashPaper为了能…

redis的lua脚本解决原子操作

使用一个简单的工具类 代码示例&#xff1a;

redis集群异步复制造成锁丢失(分布式锁)

在redisConfig配置类注入bean Configuration public class RedisConfig {Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String, Object> template new RedisTemplate<String, Object>();templat…

redis内存默认值调整

redis一般设置物理内存的3/4 redis.conf配置文件修改maxmemory这个值来调整redis的内存大小 info memory命令可用查看redis内存使用情况 info可用查询redis下的各种命令

mysql支持的存储引擎

SHOW ENGINES; 默认支持innodb&#xff0c;其他存储引擎都不支持事务 innodb存储引擎的架构&#xff1a;