connectionstring属性尚未初始化怎么解决_阿里面试官问我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,最终形成一个环状的依赖。

3880a5e3a1c4940245edcfc82aa4bbd5.png
b61a0240c216f932e16972c3205ff5b1.png

代码示例如下:

d2745ea63efb4e6d9986c9437df76db2.png

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

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

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

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

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

什么是早期引用呢?

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

362e1035d0102487133f666d50fcacdb.png

在 注入属性之前的对象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实例的流程:

5d14449ec1a427c1126cea82b0f9eff2.png

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

这个流程从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中。

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

7df9404226644a0ba420e32c561a1060.png

为什么需要三级缓存呢?

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

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

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、构造方法循环依赖能解决吗?

我们先看下示例代码:

报错信息如下:

a366307b1fe0b90f5a22dc4495d291c1.png

报错信息如上图:

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

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

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

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

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

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

总结

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

### 回答1: 这个错误提示意思是connectionstring属性没有初始化。这通常是因为在代码中没有正确地设置连接字符串,或者连接字符串的设置被覆盖或删除了。要解决这个题,需要检查代码中的连接字符串设置,并确保它们正确地初始化connectionstring属性。 ### 回答2: connectionstring属性尚未初始化是一个经常出现在开发中的错误。这个错误通常指的是程序未能正确连接到数据库。 这个错误一般是在程序中使用了没有初始化ConnectionString 属性或者 Connection 对象的错误引起的。 首先,我们需要理解什么是 ConnectionString 属性ConnectionString 属性是一个用于连接数据库的字符串,包含数据库的连接信息,如数据源、用户名、密码等。在使用数据库时,程序必须要使用 ConnectionString 属性,来指定要使用的数据库连接信息,否则程序无法正确的连接数据库。所以,如果遇到“connectionstring属性尚未初始化”这个错误,我们需要检查程序是否已经正确的设置了 ConnectionString 属性。 那么出现这个错误的情况具体有哪些呢? 1.程序中出现拼写错误 有时,这个错误可能是由于拼写错误导致的。验证连接字符串的正确性是非常重要的,所以我们需要仔细检查程序的代码,并确保连接字符串正确。 2.程序中没有创建 Connection 对象 当我们没有正确的创建 Connection 对象时,这个错误也会出现。我们需要确保 Connection 对象被正确的初始化,并被赋值到 ConnectionString 属性中。 3.程序中未指定数据源 当我们连接的数据库没有正确的指定数据源时,也会出现这个错误。我们需要确保在连接字符串中指定了正确的数据源。 总之,要解决connectionstring属性尚未初始化”这个错误,我们需要根据具体情况找到题所在,然后采用正确的方法解决。这样才能保证程序连接数据库的完整性及正确性。 ### 回答3: “connectionstring属性尚未初始化”是在使用数据库时出现的一个常见错误信息。这个错误信息的意思是连接字符串没有被正确地初始化或赋值,导致无法取得数据库连接。当应用程序启动或执行到访数据库的代码时,应用程序需要建立一个连接到数据库的连接通道,这个连接通道需要用到一个连接字符串。如果连接字符串没被正确初始化,那么就会出现这个错误信息。 解决这个题的一种方法是检查代码,查找有关连接字符串的代码,比如检查在哪里定义了连接字符串,并确保连接字符串已经正确地赋值。如果代码正确的话,还有可能是连接字符串在配置文件中配置不正确,这时候可以检查配置文件是否正确配置并且连接字符串已经正确地包含了必要的参数,如服务器名、数据库名、用户名和密码。另外,也可以尝试使用其他的连接字符串测试数据源是否正常,同时通过排除其他可能的错误,如数据库的连接时间过长、特定表或视图不存在等,可以找到题并及时修复,以确保应用程序正常运行。 最后,如果以上方法都试过了还是不能解决题,就需要考虑寻求帮助,如通过在线技术支持或咨询专业的数据库管理员来解决题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值