目录

一、什么是循环依赖?

二、构造器参数循环依赖

三、单例的setter注入循环依赖

四、多例的setter注入循环依赖

五、Spring如何解决循环依赖

六、扩展

七、总结


一、什么是循环依赖?

循环依赖,指的是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。

  • 直接依赖: 如A依赖B,B依赖A。

  • 间接依赖: 如A依赖B,B依赖C,C依赖A。

二、构造器参数循环依赖

Spring循环依赖分为构造器参数依赖和属性setter依赖,先来说说构造器参数导致的循环依赖。

首先我们定义两个Bean对象:

public class A {private B b;public A(B b) {this.b = b;}
}public class B {private A a;public B(A a) {this.a = a;}
}

然后在Spring配置文件里面注册两个Bean,并指定通过构造参数进行注入:

<!--构造器注入-->
<bean id="a" class="com.wsh.circularreference.A"><constructor-arg index="0" ref="b"/>
</bean><bean id="b" class="com.wsh.circularreference.B"><constructor-arg index="0" ref="a"/>
</bean>

编写测试类:

public class CircularReferenceDemo {public static void main(String[] args) {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-config.xml");System.out.println(applicationContext.getBean("a"));}
}

我们运行测试类,可以看到控制台抛出BeanCurrentlyInCreationException异常,详细报错信息为:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

上述例子中,Bean A依赖了Bean B,Bean B反过来也依赖了Bean A,这种情况就是发生了循环依赖。当通过构造器注入的时候,Spring将无法决定先创建哪个bean,所以Spring将产生异常BeanCurrentlyInCreationException。

Spring并没有直接解决因为构造器注入而导致的循环依赖问题,原因如下:

Java对类进行实例化的时候,需先实例化构造器的参数,Spring在创建Bean A的时候,构造器需要注入Bean B,那么Spring又会去创建Bean B,然后执行Bean B的构造器时,又发现需要实例化Bean A,从而就构成了一个环,导致Spring无法创建对象,所以就抛出BeanCurrentlyInCreationException异常。

不过,Spring提供了延迟加载机制来解决构造器循环依赖启动报错的问题。

三、单例的setter注入循环依赖

修改前面的实体类,生成对应的setter方法:

public class A {private B b;public void setB(B b) {this.b = b;}
}public class B {private A a;public void setA(A a) {this.a = a;}
}

Spring配置文件中,指定采用setter方式进行注入,并且指定bean的作用域为singleton:

<!--setter注入 单例方式-->
<bean id="a" class="com.wsh.circularreference.A" scope="singleton"><property name="b" ref="b"/>
</bean><bean id="b" class="com.wsh.circularreference.B"  scope="singleton"><property name="a" ref="a"/>
</bean>

同样运行测试类,观察控制台输出:com.wsh.circularreference.A@255316f2

我们看到,通过setter方式注入引起的循环依赖,并没有报BeanCurrentlyInCreationException异常,这是因为Spring默认帮我们解决了循环依赖,setter注入导致的循环依赖也是最常见的一种方式,后面我们会详细分析Spring是如何帮我们解决的。

四、多例的setter注入循环依赖

在Spring配置文件中,我们指定采用setter方式进行注入,但是指定bean的作用域为prototype:

<!--setter注入 多例方式-->
<bean id="a" class="com.wsh.circularreference.A" scope="prototype"><property name="b" ref="b"/>
</bean><bean id="b" class="com.wsh.circularreference.B"  scope="prototype"><property name="a" ref="a"/>
</bean>

同样运行测试类,我们发现又报错了:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

前面提到,Spring是通过提前暴露单例bean的对象工厂出去给其他Bean先使用,它有一个缓存存放着,但是对于"prototype"作用域的bean,Spring容器并不进行缓存,每次都会生成一个新对象,所以Spring无法通过提前暴露解决"prototype"作用域的setter循环依赖

五、Spring如何解决循环依赖

前面对三种循环依赖做了一个简单的演示,下面我们针对单例的setter注入导致的循环依赖,跟踪一下源码分析Spring内部是如何解决的。

在了解Spring循环依赖之前,需要知道Spring创建bean的三个重要的步骤:

  1. 实例化bean:createBeanInstance();
  2. 属性填充:populateBean();
  3. 初始化bean:initializeBean();

Spring解决循环依赖其实就是在上述的三个步骤处理的。

思路:

Spring主要是基于内部的三级缓存来解决循环依赖问题的,这里的三级缓存,其实也就是三个Map,它们之间并不存在上下级关系。

举个例子A -> B、B -> A,在实例化A的时候调用doGetBean("A"),然后进行实例化A,在实例化完A对象后,Spring通过提前把这个刚实例化好的A对象给暴露出去(实际上就是存放在一个Map中),然后执行到属性填充,发现其依赖了B的实例,此时又会调用doGetBean("B"),然后进行实例化B,同样的,实例化完B后,也会把B对象存放在Map中提前暴露出去,接着执行到B对象的属性填充,发现其依赖了A,那么此时又执行doGetBean("A"),这个时候并不是重新实例化A对象了,而是从前面提到的Map中去获取,也就是拿到提前暴露的对象,这样B对象属性就填充完了,可以执行接下来的初始化过程,B对象初始化完成后,再回过来执行A对象的属性填充,也就可能获取到B对象了,这样就解决了循环依赖的问题。

简单的流程图大体如下:

下面从Spring源码的角度看一下,具体是怎么处理的,还是以前面的A -> B,B -> A的例子进行分析。

  • (一)、getBean("a")

当我们执行applicationContext.getBean("a")这句代码时,进入到AbstractBeanFactory#doGetBean()方法执行,在AbstractBeanFactory#doGetBean()方法中,会执行Object sharedInstance = getSingleton(beanName):尝试从缓存中获取对应的bean。具体代码如下:

public Object getSingleton(String beanName) {// 尝试从缓存中加载bean对象// 第二个参数:表示是否允许bean早期依赖return getSingleton(beanName, true);
}protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lock// 尝试从一级缓存中获取对应的bean对象Object singletonObject = this.singletonObjects.get(beanName);// 如果一级缓存中不存在,并且当前单例bean处于正在创建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 如果一级缓存中没有的话,再尝试从二级缓存中获取对应的bean对象// 从早期单例对象缓存earlySingletonObjects中获取单例对象(之所称为早期单例对象,是因为earlySingletonObjects里的对象的都是通过提前曝光的ObjectFactory对象工厂创建出来的,还未进行属性填充等操作)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单例工厂ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 如果存在单例工厂,则调用单例工厂ObjectFactory的getObject()方法,创建一个beanif (singletonFactory != null) {// 调用ObjectFactory的getObject()方法,产生一个半成品bean// 因为添加三级缓存的时候执行的是: addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)),// 所以singletonFactory.getObject()真正执行的其实是:获取bean的早期引用  ===>  getEarlyBeanReference(beanName, mbd, bean)singletonObject = singletonFactory.getObject();/*** 如下可以看到,二级缓存earlySingletonObjects和三级缓存singletonFactories是互斥的*/// 将bean放置于二级缓存中(放到早期单例对象缓存中)this.earlySingletonObjects.put(beanName, singletonObject);// 删除三级缓存中对应bean的单例工厂ObjectFactory// 因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存(二级缓存)中了,所以后面获取beanName的单例对象的时候,// 可以从earlySingletonObjects二级缓存拿到,不需要再用到该单例工厂this.singletonFactories.remove(beanName);}}}}}}// 返回单例对象return singletonObject;
}

从前面的代码中,可以看到Spring获取bean对象的流程是,首先从一级缓存singletonObjects中查询是否存在指定的bean,如果没有找到,会尝试从二级缓存earlySingletonObjects中查询,如果还是没找到,再尝试从三级缓存singletonFactories中查找。

getSingleton()方法是Spring解决循环依赖的核心,里面使用了三级缓存,分别是:

三级缓存其实就是三个Map:

// 一级缓存:用于保存beanName和创建bean实例之间的关系,beanName -> bean instance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:用于保存beanName和创建bean实例之间的关系,beanName -> bean instance
//  与一级缓存的区别:当一个单例bean被放在二级缓存中后,当bean还在创建过程中,就可以通过getBean方法获取到了,目的是用来检测循环引用
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);// 三级缓存:用于保存beanName和创建bean的工厂之间的关系,beanName -> ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

下面是跟踪代码实现:

因为是新创建a对象,所以一级缓存singletonObjects为空,并且isSingletonCurrentlyInCreation("a")返回false。

所以Object sharedInstance = getSingleton(beanName)方法执行完后,返回的sharedInstance为空,如下图:

isSingletonCurrentlyInCreation():判断当前单例bean是否正在创建中。如在A的populateBean过程中依赖了B对象,此时得先去创建B对象,这时的A就是处于创建中的状态;

allowEarlyReference:是否允许从singletonFactories中通过getObject拿到对象;

  • (二)、执行getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

执行完前面的getSingleton()方法后,在下面又会执行其另一个重载的getSingleton()方法,代码如下:

if (mbd.isSingleton()) {  //单例作用域// 第二个参数是一个ObjectFactory,是一个函数式接口,当调用ObjectFactory的getObject()方法的时候,实际上调用的是createBean(beanName, mbd, args)// 也就是说在getSingleton()方法内部调用ObjectFactory的getObject()方法的时候,会回调到这里的createBean(beanName, mbd, args)创建bean,接着才会调用下面的getObjectForBeanInstance()方法// 8、第八步:尝试从缓存中获取对应的bean实例,获取不到的话,则执行singletonFactory的回调 -> createBean()创建beansharedInstance = getSingleton(beanName, () -> {try {// 创建bean对象return createBean(beanName, mbd, args);} catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.// 创建失败则销毁destroySingleton(beanName);throw ex;}});// 返回beanName对应的实例对象bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

进入getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法,同样的,在一级缓存singletonObjects中找不到实例a。

接着往下执行beforeSingletonCreation("a"),将"a"加入到了正在创建中的bean集合singletonsCurrentlyInCreation中,如下图:

  • (三)、执行singletonObject = singletonFactory.getObject()回调

接着前面的代码,接着执行singletonFactory.getObject()回调,这里其实是调用的入参createBean("a"),执行创建对象a的流程。如下图:

接着会进入doCreateBean("a")真正创建a对象,然后通过createBeanInstance()实例化a对象。如下图:

  •  (四)、boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName))

这里是判断bean是否需要提前暴露:

  • mbd.isSingleton():bean是单例的;
  • this.allowCircularReferences:允许循环依赖;
  • isSingletonCurrentlyInCreation(beanName):对象a是否正在创建中;

因为前面已经把"a"加入到正在创建中bean缓存singletonsCurrentlyInCreation中了,所以这里isSingletonCurrentlyInCreation(beanName))返回true,也就是earlySingletonExposure返回为true,表示需要提前暴露刚刚实例化好的a对象

  • (五)、addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))提前暴露对象

earlySingletonExposure如果为true的话,会执行提前暴露a对象的逻辑:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)),代码如下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");// 加锁synchronized (this.singletonObjects) {// 1、如果一级缓存中不存在当前beanName的时候,才能进if判断if (!this.singletonObjects.containsKey(beanName)) {// 2、将beanName => ObjectFactory的映射关系添加到三级缓存中,注意添加的是创建bean的对象工厂singletonFactorythis.singletonFactories.put(beanName, singletonFactory);// 3、从二级缓存中移除当前beanNamethis.earlySingletonObjects.remove(beanName);// 4、将beanName添加到已注册单例集合中this.registeredSingletons.add(beanName);}}
}

通过这个方法,将创建a对象的对象工厂ObjectFactory存入了三级缓存中,并移除二级缓存中对应的缓存。如下图:

addSingletonFactory()方法发生在createBeanInstance()实例化对象之后,也就是说单例对象的构造器已经被调用了,实例已经被创建出来。虽然这个对象还没完成属性填充和初始化过程,属于一个半成品对象,但是Spring将创建这个bean的对象工厂提前暴露了出来,这样其他对象就可以拿到提前暴露出来的对象工厂进行创建对象,然后进行属性填充。

  • (六)、populateBean(beanName, mbd, instanceWrapper)属性填充

提前暴露完成后,会执行populateBean("a"),填充对象a的属性,发现其依赖了b对象,此时需要执行getBean("b")从容器中获取b对象,跟前面类似,执行doGetBean("b")和Object sharedInstance = getSingleton("b"),如下图:

同样的,因为第一次创建b对象,所以singletonObjects一级缓存、singletonsCurrentlyInCreation中都不存在b对象。

  • (七)、执行getSingleton("b", ObjectFactory<?> singletonFactory)
if (mbd.isSingleton()) {  //单例作用域// 第二个参数是一个ObjectFactory,是一个函数式接口,当调用ObjectFactory的getObject()方法的时候,实际上调用的是createBean(beanName, mbd, args)// 也就是说在getSingleton()方法内部调用ObjectFactory的getObject()方法的时候,会回调到这里的createBean(beanName, mbd, args)创建bean,接着才会调用下面的getObjectForBeanInstance()方法// 8、第八步:尝试从缓存中获取对应的bean实例,获取不到的话,则执行singletonFactory的回调 -> createBean()创建beansharedInstance = getSingleton(beanName, () -> {try {// 创建bean对象return createBean(beanName, mbd, args);} catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.// 创建失败则销毁destroySingleton(beanName);throw ex;}});// 返回beanName对应的实例对象bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

接着,进入getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法,跟前面类似,这里会将"b"加入到正在创建中bean集合缓存singletonsCurrentlyInCreation中,如下图:

  •  (八)、singletonObject = singletonFactory.getObject()执行回调

接着,会执行singletonObject = singletonFactory.getObject(),实际上是执行入参回调createBean("b")创建b对象的流程,如下图:

  • (九)、createBeanInstance("b"):实例化b对象

接着执行到:

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));

因为"b"前面已经加入到singletonsCurrentlyInCreation缓存中,所以这里earlySingletonExposure返回true,表示需要提前暴露b对象,则执行:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))提前暴露,如下图:

同样的,将创建b的对象工厂加入到了三级缓存singletonFactories中。

  • (十)、populateBean("b", mbd, instanceWrapper)属性填充

提前暴露完成后,执行populateBean(beanName, mbd, instanceWrapper)对b进行属性填充,发现其依赖了a,所以通过getBean("a")去容器中找对象a,这里又回到doGetBean("a")方法了,再一次执行Object sharedInstance = getSingleton(beanName):

我们发现,因为前面已经将a对象对应的对象工厂ObjectFactory都添加到三级缓存中了,所以这里能拿到a对象对应的ObjectFactory。

获取到对象工厂后,执行singletonObject = singletonFactory.getObject()回调,实际上是执行:getEarlyBeanReference("a")获取提前暴露的对象a的引用。如下图:

执行完singletonObject = singletonFactory.getObject()回调后,接下来会执行下面的代码:

  • 将对象a添加到二级缓存中;
  • 移除三级缓存中对象a对应的ObjectFactory对象工厂;
// 将bean放置于二级缓存中(放到早期单例对象缓存中)
this.earlySingletonObjects.put(beanName, singletonObject);// 删除三级缓存中对应bean的单例工厂ObjectFactory
// 因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存(二级缓存)中了,所以后面获取beanName的单例对象的时候,
// 可以从earlySingletonObjects二级缓存拿到,不需要再用到该单例工厂
this.singletonFactories.remove(beanName);

如下图:

执行完前面一系列的过程,已经获取到对象a了,此时对象b的属性填充过程已经完成了,接下来就可以执行initializeBean("a")初始化a对象了,这里就不详细介绍了,跟循环依赖关联不大。

  • (十一)、addSingleton(beanName, singletonObject)

b对象初始化完成后,会执行addSingleton("b", singletonObject):

具体代码如下:

protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {// 将bean保存到一级缓存中this.singletonObjects.put(beanName, singletonObject);// 删除三级缓存对应的beanthis.singletonFactories.remove(beanName);// 删除二级缓存中对应的beanthis.earlySingletonObjects.remove(beanName);// 记录当前所有已注册的beanthis.registeredSingletons.add(beanName);}
}

执行完addSingleton()方法后,三个缓存对应的值如下图所示:

可以看到,b对象初始化完成后,被加入到了一级缓存中,同时从三级缓存中移除掉b对象对应的对象工厂。

  • (十二)、a初始化完成

b初始化完了,接着回来给a对象的b属性赋值,由于前面已经把对象b存入一级缓存中了,所以getSingleton("b")可以直接拿到b对象,直接进行属性填充,这样a对象也经历后续的初始化过程,初始化完成后,也会调用addSingleton("a")将对象a添加到一级缓存中,同时移除二级缓存、三级缓存中对应的缓存。

至此,循环依赖就解决了。

六、扩展

这里主要是总结一下Spring循环依赖常见的一些问题。

【1】为什么Spring不能解决构造器的循环依赖?

从前面的分析可以看到,单例bean是在实例化后,也就是执行了构造方法后,才提前暴露的,因此使用构造器注入的话,三级缓存中并没有存放提前暴露的对象,因此getBean的时候,都不能从缓存中获取,所以Spring无法解决构造器参数注入导致的循环依赖。

【2】为什么prototype原型Bean不能解决循环依赖?

作用域为prototype的Bean并不是在容器启动的时候开始bean的生命周期的,而是在用到的时候调用doGetBean方法才会走生命周期流程,从源码中可以看到,只有单例bean才使用了三级缓存,原型bean是没有缓存的,所以Spring无法解决prototype原型bean属性注入导致的循环依赖。

【3】如果只有一级缓存,能不能解决循环依赖问题?

不能。我们都知道一级缓存存放的是完整对象(实例化完成、属性填充完成、初始化完成),如果只有一级缓存的话,意味着半成品对象(实例化完成、属性未填充、未初始化)需要跟完整对象放在一起,这样调用getBean()就有可能拿到的是半成品对象,属性的值都是null。所以只有一级缓存的话,是不能解决循环依赖问题的。

【4】如果只有一级缓存、三级缓存的话,能不能解决循环依赖问题?

只使用一级缓存、三级缓存这两个缓存确实可以解决循环依赖,但是有一个前提,这个bean没被AOP进行切面代理。

  • 如果不涉及到代理的话,只有一级缓存、三级缓存是可以解决循环依赖的。一级缓存存放完整对象,三级缓存存放提前暴露出来的对象。
  • 但是如果涉及到代理的时候,就不行了,因为三级缓存中的ObjectFactory每执行一次,就会新创建一个对象,不能解决循环依赖问题。

【5】为什么需要使用二级缓存earlySingletonObjects?

如果没有涉及到AOP代理,二级缓存好像显得有点多余。但是如果使用了AOP代理,那么二级缓存就发挥作用了。前面提到,三级缓存singletonFactories中存放的是ObjectFactory对象工厂,当执行singleFactory.getObject()回调的时候,实际上会执行getEarlyBeanReference()方法获取bean的早期引用,但是我们需要注意的是,每次执行singleFactory.getObject()方法都会重新产生一个新的代理对象,这就有问题了,因为我们的bean是单例的,不可能每次都来一个新的代理对象。

所以,Spring引入了二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存earlySingletonObjects中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。

所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。

【6】三级缓存中为什么保存ObjectFacory对象,而不是保存原始的实例对象?

举个例子,假设直接将原始对象X存入三级缓存中,那么其他对象如Y依赖了X,在注入的时候,Y需要的X不是原始的X对象,这样就可能有问题。

如果存入的是一个ObjectFactory对象工厂,那么我们可以根据ObjectFactory生产任何bean对象,ObjectFactory是Spring留给我们进行扩展的,有可能我们需要对bean进行代理或者其他操作。如当调用singletonFactory.getObject()方法的时候,会执行getEarlyBeanReference()方法,里面可以通过BeanPostProcessor后置处理器增强bean。

七、总结

根据以上的分析,大概清楚了Spring是如何解决循环依赖的,读者在分析循环依赖的前提,最好对Spring创建bean的流程有一个比较好的了解,这样对循环依赖梳理起来就比较容易。

最后,通过一张图来总结一下Spring解决循环依赖的详细过程,这张图是笔者跟踪循环依赖处理流程时画的,图中涉及细节比较多,建议读者对照着源码一点点看图,相信多看几遍就能理清其中的处理逻辑了。

希望本篇文章对大家有所帮助,最后也希望读者能指出文章不对之处。

Spring循环依赖原理相关推荐

  1. 【spring容器启动】之bean的实例化和初始化(文末附:spring循环依赖原理)

    本次我们通过源码介绍ApplicationContext容器初始化流程,主要介绍容器内bean的实例化和初始化过程.ApplicationContext是Spring推出的先进Ioc容器,它继承了旧版 ...

  2. Spring循环依赖产生原理

    目录 1 概述 2 Spring循环依赖介绍 2.1 为什么会产生循环依赖 2.2  Spring IOC加载过程 2.3 spring一二三级缓存 2.3.1 一级缓存作用 2.3.2 二级缓存作用 ...

  3. Spring 循环依赖(circular dependency)

    一.什么是循环依赖 循环依赖即循环引用,形成闭环.比如,A 依赖 B,B 又依赖 A,形成了循环依赖:或者 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖:更或者是自己依赖自己.如图: 这 ...

  4. 这个 Spring 循环依赖的坑,90% 以上的人都不知道

    点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:Mythsman blog.mythsman.com/post/5d838c7c2d ...

  5. Spring循环依赖的三种方式以及解决办法

    Spring循环依赖的三种方式以及解决办法 [转]https://www.cnblogs.com/liuqing576598117/p/11227007.html 示例 https://github. ...

  6. 聊透Spring循环依赖

    本文聊一下和依赖注入密切相关,并且在实际开发中很常见,面试也很喜欢问的一个问题:Spring是怎么解决循环依赖的?  之前就被问过Spring是怎么解决循环依赖的问题,当时年少无知,对Spring源码 ...

  7. 22-10-14 西安 spring循环依赖、对象内存布局、synchronized锁升级

    关于锁升级参考了周阳老师在b站的juc视频,阳哥讲的很好 尚硅谷2022版JUC并发编程(对标阿里P6-P7)_哔哩哔哩_bilibili spring循环依赖 1.循环依赖问题 什么是循环依赖 默认 ...

  8. 详解Spring循环依赖

    一. 什么是循环依赖 循环依赖,就是两个或则两个以上的bean互相依赖对方,最终形成闭环.比如"A对象依赖B对象,而B对象也依赖A对象",或者"A对象依赖B对象,B对象依 ...

  9. Spring循环依赖详解

    Spring循环依赖详解 什么是循环依赖 spring是如何解决循环依赖 循环源码分析 getSingletion方法 getSingleton spring开启代理对象的地方 循环依赖的限制条件 什 ...

最新文章

  1. 先庆祝一下,冠军的心博客园诞生了!!
  2. 成功解决 ValueError: feature_names mismatch training data did not have the following fields
  3. Tomcat方面的知识点
  4. 30分钟快速搭建移动应用直传OSS服务
  5. GPCC安装以及踩坑经历
  6. Windows Phone(wp7)系统长按的秘密
  7. 记XMPP即时通讯协议的认识
  8. 二进制数的算术运算和逻辑运算
  9. cron表达式每隔1小时一次_quartz 每天 8-10点每隔10分钟执行一次,11-13点每隔1小时执行一次 cronExpression 要怎么写?...
  10. mysql是如何设置候选码_求关系模式中的候选键(软考,数据库)
  11. HTML为图片添加Alt描述,图片标签alt
  12. Captain Flint and a Long Voyage
  13. wordpress插件_9个最佳WordPress产品组合插件
  14. 79g道闸雷达_79GHz道闸雷达调试软件
  15. excel表格分割线一分为二_Anki+思维导图的两种方法(Anki+表格,Anki+幕布)
  16. 写在Doris毕业后的第一天
  17. 结构方程模型amos软件一些常见的处理问题
  18. SVM实战之垃圾邮件过滤
  19. 三国志战略版狼盟和鸿蒙,黄沙百战穿金甲,不破狼盟誓不还 (鸿蒙战帖—致敬一起的战友)...
  20. 小白进阶之Scrapy安装.使用.爬取顶点小说信息

热门文章

  1. 周鸿祎构建360客户端帝国:可进军搜索战百度
  2. 开源的fortran编译器LFortran
  3. 10大理由告诉你为什么要学习Python
  4. iOS收起键盘的常用方法
  5. IT软件测试 怎么样?
  6. Linux fdisk命令详解:给硬盘分区
  7. 学生用计算机能算方差吗,用科学计算器计算方差和标准差
  8. grep 同时排除多个关键字
  9. 11月编程语言排行榜出炉,Python排名势如破竹
  10. c语言lna,恩智浦半导体推出SiGe:C低噪声放大器(LNA)