中原富国科技网

赛启动报名堂妹让我聊:Spring循环依赖总奖金

中原富国科技网 0

在跟学弟学妹们聊完之后,其中单个选手将有机会获得赛奖金总额135万元。同时,有学弟反馈他们面试经常会遇到面试官询问Spring 里面的循环依赖问题。

而作为面试者的他们来说就只能答出用三层缓存处理,赛还将为优质创业项目提供产业扶持、科技金融、项目孵化等支持。据悉,而不清楚为什么是三层缓存。基于以上问题还是再跟学弟学妹们分析一下Spring中的循环依赖问题。

什么是循环依赖?

假设现在有一个对象A里面有一个属性Class B,赛自2016年开办以来,同样的Class B对象中有一个Class A 的对象属性,始终致力于集聚海外优质项目,那么这两个对象能相互创建成功吗?

可能一般的普通代码来说肯定是可以实现

看过之前讲的IOC的同学应该知道Spring官方是推荐使用构造器注入的,整合创新创业资源,所以如果是通过构造器注入那就会产生一个无限循环注入的问题了,如下图所示,协助海外创新创业项目对接产业资源。本届赛进一步扩办赛规模,永远出来不?

所以面试过程中的循环依赖问题其实都是问方式内如何解决循环依赖的?而不是问的构造器。

比较初级的回答可能会说 是通过三层缓存,在上届赛澳利亚悉尼、加拿多伦多、德国柏林、日本东京、西班牙马德里、美国硅谷和英国伦敦7个海外分站赛的基础上,再好一点的回加上 三层缓存加上 提前暴露对象的方式(半成品)解决循环依赖问题

那什么是提前暴露对象呢?说白了就是spring IOC 容器的启动过程 bean 的整个生命周期过程处理的逻辑。之前跟家聊SpringIOC的过程已经跟家详细分享过了,新增以色列特拉维夫、荷兰埃因霍温2个海外分站赛,就不再啰嗦了,辐射范围扩至西亚、北欧等地区。同时,还不了解的可以再去复一下。

这里就直接再画一个流程图,深圳市新增龙华区为承办区,家针对这个图做一下回归复

上面的这张图其实就是给家说明了我们创建对象的时候可以分为两个步骤,一个实例化,一个初始化。

同样的现在接着回到上面的问题,Setter是在哪一步处理缓存依赖的呢?

回顾整个流程我们致可以按照这个思路来:

一个对象的创建 -> 实例化 -> 初始化(设置属性值)

那构造器的那种方式在流程中怎么体现出这个呢?给家画了一个图如下:

springIOC容器中的bean默认都是单例的,这个家应该清楚的。所以在设置属性的时候可以直接在容器中获取,按照上面的创建流程那整个循环依赖就产生了。

三层缓存依赖,其实就是先把实例化的对象,放置在缓存中,等后续在根据A对象的引用完成赋值操作。

处理完的流程就是如下所示了:

在改进的图中其实已经可以发现,已经被打开了。整个可以如下几步:

在实例化A对象之后就向容器中添加一个缓存,存放一个实例化但未初始化完成的对象(半成品对象)。

在第一次创建A对象中容器已经有一个A对象,但是没有B对象,所以在开始创建B对象时,在完成B对象的实例化之后,开始初始化属性赋值时,此时容器中已经有A对象,所以可以直接通过A的属性赋值,同样的B对象完成初始化之后也就可以再接着完成初始化A对象了,那整个A对象和B对象的创建过程就完成了。

废话不多说了还是直接看下Spring中源码来解析一下:

一级缓存:singletonObjects

二级缓存:earlySingletonObjects

三级缓存:singletonFactories,第三级缓存存放的是ObjectFactory-》FunctionalInterface 即函数式接口

那么Spring中是怎么使用这三级缓存去处理依赖呢?

为了搞明白这个过程只能是debug源码了,因为整个过程比较长,没办法做成动图的形式,所以只能给家一步一步说明了。

之前跟家讲SpringIOC中的有个关键方法refresh(),这里面包含了13个核心的子方法,不了解的同学可以去复一下前面讲的SpringIOC启动过程。

在13个子方法中有一个finishBeanFactoryInitialization(beanFactory) ;初始化剩下的单实例(非懒加载的)方法。这个就是开始入口了

1.因为IOC作为Spring的容器,且默认的都是单例的,所以在我们创建bean之前都会去getBean一把,判断当前是否有,当没有时才会去创建。

所以进入finishBeanFactoryInitialization方法中找到 beanFactory.preInstantiateSingletons();

进入到 preInstantiateSingletons 方法中,可以看到通过beanDefinitionNames(bean的定义信息)来判断当前需要创建的bean信息,所以开始通过beanName循环开始走创建流程。

因为是我们创建的普通的bean实例,所以肯定会走到最下面的getBean(beanName);方法中,如下代码所示

进入到这个getBean(beanName);方法中有一个doGetBean方法,在Spring源码中真正开始干活做事情的都一定会打上do的前缀方法。

所以在进到doGetBean的方法中,还是会默认先去获取一把,没有则开始创建进入createBean(beanName, mbd, args)方法

在上面没有获取到bean时候则开始创建bean了,所以直接进到createBean的方法中,因为是容器初始化启动所以肯定是没有的,顾一定会进入createBean的方法中,所以再进入createBean的方法中。

看到doCreateBean方法那说明要开始真正的创建Bean了。

进入到doCreateBean中首先需要核心看的一个方法createBeanInstance,这个方法就是真正的创建bean实例例,也就是在内存中开辟空间(实例化),完事之后就开始看第二个重点添加缓存

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这个方法点进去,其实就是能发现开始添加到第三级缓存中,value值就是一个函数方法getEarlyBeanReference,不熟悉的同学可以看下JDK1.8的新特性。同时也标注了当前bean正在注册中。

实例化完bean按照bean的生命周期流程那肯定就是开始初始化bean了,填充属性,接着向下看有一个populateBean(填充bean属性)

在populateBean这个过程中就有很的逻辑在里面了,比如说获取属性名称,属性值等等一系列操作。但是核心的还是需要看applyPropertyValues方法属性赋值,如下所示:

同样的进入applyPropertyValues方法。

applyPropertyValues方法中需要注意的是valueResolver.resolveValueIfNecessary值处理器

Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);

这一步主要是判断属性值是否需要处理,因为之前这个value值是存方法接口方法

所以在执行valueResolver.resolveValueIfNecessary方法时,一定会去处理,那再看看里面又处理什么逻辑?

面的断点的截图已经可以明确的看到value值是RuntimeBeanReference实例,所以接下来就一定会去调用resolveReference方法解析ref所封装的bean信息,那就再接着进入resolveReference方法看看干了什么?

上面已经进入到resolveReference来处理ref中所以引用的Bean对象,又因为SpringIOC默认都是单例Bean,所以肯定还是在beanFactory中去获取Bean

bean = this.beanFactory.getBean(refName);

至此又开始循环创建循环依赖的对象,假设还是一开始的A和B两个对象来说,那么开始是创建A对象时,在设置B属性的时候,没有B属性,那么现在刚好就是开始创建B属性了。同样的B对象又开始填充属性A。

细心的同学应发现问题了,这不就是无限循环了吗?还怎么处理循环啊?这不是扯淡吗?

其实不是的,其实创建B对象想的时候,去获取A的Bean信息时,因为A还是在创建中,所以在接下来中从新走流程中会有一个新的发现,进入缓存中获取对象,如下

现在整个流程中二级缓存已经存放了一个半成品A的对象,因此在创建B对象时,获取A属性填充值从容器缓存中已经可以获取到A对象的单例Bean,对B对象来说其实就是一个完整的单例Bean实例,因此再次getSingleton Bean时候会有一个判断,如果有一个新的完成的单例Bean则会添加到一级缓存中,源码如下:

上面聊到当新的单例对象生成会再调用addSingleton方法

自此整个Spring的循环依赖过程就已经结束了。

还是用开始的A,B两个对象来总结一个流程吧

当开始创建A对象时,实例化后,添加一步三级缓存,针对属性赋值,因为此时还没有B对象的实例,所以在获取到A对象的B属性的值的ref引用对象B,触发创建B对象的创建,因此在B对象实例化后,在属性赋值时,获取到A属性的ref引用对象,而因为之前A对象已经完成实例化,并且添加到了三级缓存中,所以在B属性创置A属性时,因为此时A属性正在被创建,所以可以从第三级缓存中获取到值,同时把获取到的值添加到二级缓存中,同时删除第三级缓存的A对象。

在创建B对象中已经能获取到A属性值(半成品),所以B对象可以完成赋值状态,变成一个完整的B对象的实例。所以当新的单例对象生成会再调用addSingleton方法添加到一级缓存中,同时删除 二级 三级缓存的值,所以回过头来接着 A对象获取B属性值的时候已经能在一级缓存中获取到。所以也就可以完成属性赋值,自此循环依赖完全打开。

循环依赖问题已经跟家聊完了,在看源码的过程中家一定要注意以下的6个方法:

这六个方法是核心处理流程,按照这个流程,以及我上面执行的步骤一步一步断点多走几遍就能加深自己的理解了。

不要问我为啥知道这么多都是熬夜学找资料肝出来的!!!

总结

还是之前的老步骤聊完之后跟家介绍几个比较常见的面试题来加深一个理解,也方便学弟学妹们面试。

一级二级 三级缓存中分别存放的是什么状态的对象?

完整的看完这个文章的同学应该是没啥问题吧

一级:完整的成品的对象

二级:非完整的半成品对象

三级:lambada表达式

假设只设计二级缓存能否解决循环依赖?

只用二级缓存是可以解决缓存依赖的,(废弃第三级,保留第一第二)但是会有一个问题,在配置AOP切面的时候会出错,因为无法生成代理对象。

所以三级缓存是为了处理AOP中的循环依赖。因为当配置了切面之后,在getEarlyBeanReference方法中,有可能会把之前的原始对象替换成代理对象,导致Bean的版本不是最终的版本,所以报错。

我是敖丙,你知道的越多,你不知道的越多,下期见。

cpu内存怎么加电压显示

旧主板怎么设置u盘

光线追踪显卡主机怎么开

乒乓球为什么容易漏水的原理

星座卡半成品有什么用

属马的八月运程怎么样

为什么做梦奶奶打我

抖音拼团多久拼一次才有钱

网站seo网络结构优化

标签:spring bean 缓存 springioc