阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!

目录:

1、简介

2、什么是循环依赖?

3、Spring 是如何解决循环依赖的?

4、Spring 解决循环依赖的源码分析

5、为什么需要三级缓存呢?

6、循环依赖的其他情况

7、总结

简介

下文将介绍Spring是如何解决循环依赖的,在下文中将要介绍以下什么是循环依赖。以及从源码层面上分析Spring是如何解决的,最后会介绍哪几种情况下的循环依赖 是Spring 解决不了的。

Spring 如何解决循环依赖也是高频面试题,面试官的可以考察你对循环依赖的理解,以及你对源码的熟悉程度,以及哪些情况下的循环依赖是Spring解决不了的。

什么是循环依赖?

所谓的循环依赖就是A依赖B,B依赖A,或者是A依赖B,B依赖C,C依赖A,最终形成一个环状的依赖。

阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!


阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!

代码示例如下:

阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!

从上面的代码看,如果Spring不处理循环依赖,会怎么样?

IOC容器在读取上面配置时,首先会初始化对象A,然后发现对象A依赖对象B,就去初始化对象那个B,初始化对象B的时候,又发现依赖A,需要初始化A,如果容器不去处理,那么将会无限循环下去 ,直到内存溢出。

那么Spring是如何解决循环依赖的呢?

Spring 是如何解决循环依赖的?

在初始化A时,即在调用构造方法时,会 产生一个早期引用,存在缓存中,然后A发现需要依赖B对象,容器再去初始化B对象,B对象初始化后(调用构造方法,也会产生早期引用,存在缓存中),A这时就得到了B的对象(已实例化,但是还没有注入属性),然后B对象发现需要依赖A对象,这时A对象已经有一个早期引用了,所以B直接依赖对象A,

什么是早期引用呢?

即 对象实例化后(调用构造方法),还没有进行属性注入,如下图:

阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!

在 注入属性之前的对象A就是早期对象

Spring 依赖是通过三级缓存来实现的,分类如下:

1、三级缓存(singletonFactories)

用于存放 beanName和 初始化好的bean对象(属性已经初始化好的)

private final Map singletonObjects = new ConcurrentHashMap(256);

用于存放bean工厂。bean 工厂所产生的bean是还未完成初始化的bean.bean工厂所生成的对象最终会被缓存到earlySingletonObjects(二级缓存)中,包裹对象ObjectFactory.getObject()早期对象。

放入时机:调用类的构造方法初始化之后。

ObjectFactory.getObject()->触发getEarlyBeanReference()之后,才把我们的早期对象放入二级缓存中。

2、二级缓存(earlySingletonObjects)

存放 bean 工厂对象,用于解决循环依赖

private final Map> singletonFactories = new HashMap>(16);

在三级缓存放入二级缓存中调用:

早期对象:就是bean刚刚调用了构造方法,还来不及给bean对象的 属性进行赋值的对象

3、一级缓存(singletonObjects)

用于存放beanName 和一个原始bean 早期bean(属性未初始化)

private final Map earlySingletonObjects = new HashMap(16);

用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用。

Spring 解决循环依赖的源码分析

我们来了解一下Spring IOC容器获取bean实例的流程:

阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!

我们来分析一下上图的流程:

这个流程从getBean开始,所有的逻辑都在doGetBean中,doGetBean首先调用getSington方法获取bean实例,获取的实例分为三种情况:

(1) 完全实例化好的bean(此时bean的属性已经注入)

(2) 早期对象(bean已经调用构造方法进行初始化,但是属性还没有注入)

(3) 各级缓存中都没有,则返回null

如果不为null,则调用 getObjectForBeanInstance 方法来返回bean。getObjectForBeanInstance 方法作用是如果Bean是FactoryBean,则调用其 getObject()方法。

如果bean为null,则 调用getSingleton方法,

sharedInstance = getSingleton(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

try {

return createBean(beanName, mbd, args);

}

}

});

最终会调用createBean方法,进行bean的实例化。

首先看下调用链;

AbstractBeanFactory# doGetBean

1、从缓存中获取bean对象

Object sharedInstance = getSingleton(beanName);

方法如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

//第一步

Object singletonObject = this.singletonObjects.get(beanName);

i

f (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

singletonObject = this.earlySingletonObjects.get(beanName);

if (singletonObject == null && allowEarlyReference) {

ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);

if (singletonFactory != null) {

singletonObject = singletonFactory.getObject();

this.earlySingletonObjects.put(beanName, singletonObject);

this.singletonFactories.remove(beanName);

}

}

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

执行步骤如下:

第一步: 我们尝试从一级缓存中去获取对象,一般直接从map中获取是可以直接使用的,IOC容器初始化加载单实例bean的 时候第一次进来,一般为空

第二步: 若在一级缓存中没有获取到对象,并且此bean正在初始化,singletonsCurrentlyInCreation这个list包含该beanName,主要用于判断bean是否在创建中。

第三步:尝试去二级缓存中获取对象(二级缓存中的 对象也是一个早期对象(已经调用构造方法,但是还没有对属性赋值)。

第四步:二级缓存中也没有,那就直接从三级缓存中获取ObjectFactory对象,这个对象就是用来解决循环依赖 的关键所在,

第五步:如果有三级缓存,则调用ObjectFactory包装对象中,通过调用它的getObject()来获取我们的早期对象,然后把早期对象放入二级缓存,从三级缓存中删除掉。

2、如果缓存中没有获取到对象,下面就开始创建对象

if (mbd.isSingleton()) {

sharedInstance = getSingleton(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

try {

return createBean(beanName, mbd, args);

}

catch (BeansException ex) {

throw ex;

}

}

});

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

}

public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {

synchronized (this.singletonObjects) {

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null) {

try {

//调用getObject-->createBean()去创建对象

singletonObject = singletonFactory.getObject();

newSingleton = true;

}

if (newSingleton) {

//把创建好的对象加入到缓存中,并且把早期对象从缓存中删除,此时bean已经完全初始化好

addSingleton(beanName, singletonObject);

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

}

protected void addSingleton(String beanName, Object singletonObject) {

synchronized (this.singletonObjects) {

// 放入到缓存对象中

this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

//早期对象逸出

this.singletonFactories.remove(beanName);

this.earlySingletonObjects.remove(beanName);

this.registeredSingletons.add(beanName);

}

}

上面的代码主要用来创建对象,调用ObjectFactory.getObject()方法创建对象,创建完成后,把早期 对象从缓存中删除。

3、下面看创建对象的方法

AbstractAutowireCapableBeanFactory#doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)

throws BeanCreationException {

// Instantiate the bean.

BeanWrapper instanceWrapper = null;

// 调用构造方法创建bean对象,并且把bean对象包裹为beanwrapper对象

instanceWrapper = createBeanInstance(beanName, mbd, args);

// earlySingletonExposure 用于表示是否提前暴露原始对象的引用,用于解决循环依赖。

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&

isSingletonCurrentlyInCreation(beanName));

if (earlySingletonExposure) {

//把beanName -->ObjectFactory 存放在 singletonFactories缓存中

addSingletonFactory(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

return getEarlyBeanReference(beanName, mbd, bean);

}

});

}

// Initialize the bean instance.

Object exposedObject = bean;

try {

//早期对象进行赋值

populateBean(beanName, mbd, instanceWrapper);

if (exposedObject != null) {

//调用bean的后置处理器以及bean的初始化方法

exposedObject = initializeBean(beanName, exposedObject, mbd);

}

}

return exposedObject;

}

上面方法是真正创建对象的方法,步骤如下

1、调用bean的构造方法进行初始化

2、加入三级缓存中

3、早期对象进行赋值

4、调用Bean的后置处理器以及bean的初始化方法

下面看addSingletonFactory方法源码:

protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) {

synchronized (this.singletonObjects) {

if (!this.singletonObjects.containsKey(beanName)) {

this.singletonFactories.put(beanName, singletonFactory);

this.earlySingletonObjects.remove(beanName);

this.registeredSingletons.add(beanName);

}

}

}

上述方法是把暴露的objectFactory对象放入singletonFactories中。

以上的过程对应的流程图为:

阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!

为什么需要三级缓存呢?

有人说,二级缓存不就够了吗?

答案是:二级缓存足够了,但是没有很好的扩展性,我们看下加入三级缓存的源码:

addSingletonFactory(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

return getEarlyBeanReference(beanName, mbd, bean);

}

});

那么 从缓存中获取bean的代码:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

singletonObject = this.earlySingletonObjects.get(beanName);

if (singletonObject == null && allowEarlyReference) {

ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);

if (singletonFactory != null) {

singletonObject = singletonFactory.getObject();

this.earlySingletonObjects.put(beanName, singletonObject);

this.singletonFactories.remove(beanName);

}

}

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

从上面代码可以看出,当获取三级缓存时,需要使用objectFactory.getObject()方法,最终会调用

getEarlyBeanReference(beanName, mbd, bean);方法,

getEarlyBeanReference 方法源码:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

Object exposedObject = bean;

if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {

for (BeanPostProcessor bp : getBeanPostProcessors()) {

if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {

SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;

exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

if (exposedObject == null) {

return null;

}

}

}

}

return exposedObject;

}

getEarlyBeanReference()方法的作用是:经过一系列的后置处理来给我们早期对象进行特殊化处理

从三级缓存中获取包装对象的时候 ,他会经过一次后置处理器的处理对我们早期对象的bean进行

特殊处理,但是Spring原生后置处理器没有经过处理,是留给我们的扩展接口。

循环依赖的其他情况

1、构造方法循环依赖能解决吗?

我们先看下示例代码:

报错信息如下:

阿里面试官问我Spring是如何解决循环依赖的时候,我一脸懵逼了!

报错信息如上图:

大概意思是:Spring 解决不了构造方法的循环依赖。

那么为什么Spring不能解决循环依赖呢?

从我们上面分析可知,如果是成员属性有循环依赖,那么Spring是可以解决的,是因为有早期对象的存在才可以解决,早期对象就是实例化后的对象(即调用构造方法后的实例)。

如果构造方法中循环依赖,则无法生成早期对象。所以,Spring无法解决构造方法依赖。

2、多例的循环依赖能解决吗?

因为Spring只缓存单例对象,而不缓存多例对象,所以无法从缓存中存活

总结

到这里,本编文章就基本上写完了。由于本人技术能力有限,若文章有错误不妥之处,欢迎大家指出来。好了,本篇文章到此结束,谢谢大家的阅读。


分享到:


相關文章: