目录
一、什么是循环依赖?
二、构造器参数循环依赖
三、单例的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,并指定通过构造参数进行注入:
编写测试类:
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异常。
三、单例的setter注入循环依赖不过,Spring提供了延迟加载机制来解决构造器循环依赖启动报错的问题。
修改前面的实体类,生成对应的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:
同样运行测试类,观察控制台输出:com.wsh.circularreference.A@255316f2
我们看到,通过setter方式注入引起的循环依赖,并没有报BeanCurrentlyInCreationException异常,这是因为Spring默认帮我们解决了循环依赖,setter注入导致的循环依赖也是最常见的一种方式,后面我们会详细分析Spring是如何帮我们解决的。
四、多例的setter注入循环依赖在Spring配置文件中,我们指定采用setter方式进行注入,但是指定bean的作用域为prototype:
同样运行测试类,我们发现又报错了:
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的三个重要的步骤:
- 实例化bean:createBeanInstance();
- 属性填充:populateBean();
- 初始化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 lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 如果二级缓存中也没有的话,再尝试从三级缓存中获取对应的ObjectFactory单例工厂 ObjectFactory> singletonFactory = this.singletonFactories.get(beanName); // 如果存在单例工厂,则调用单例工厂ObjectFactory的getObject()方法,创建一个bean if (singletonFactory != null) { // 调用ObjectFactory的getObject()方法,产生一个半成品bean // 因为添加三级缓存的时候执行的是: addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)), // 所以singletonFactory.getObject()真正执行的其实是:获取bean的早期引用 ===> getEarlyBeanReference(beanName, mbd, bean) singletonObject = singletonFactory.getObject(); // 将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 MapsingletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:用于保存beanName和创建bean实例之间的关系,beanName -> bean instance // 与一级缓存的区别:当一个单例bean被放在二级缓存中后,当bean还在创建过程中,就可以通过getBean方法获取到了,目的是用来检测循环引用 private final Map earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:用于保存beanName和创建bean的工厂之间的关系,beanName -> ObjectFactory private final Map > 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()创建bean sharedInstance = 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的对象工厂singletonFactory this.singletonFactories.put(beanName, singletonFactory); // 3、从二级缓存中移除当前beanName this.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()创建bean sharedInstance = 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); // 删除三级缓存对应的bean this.singletonFactories.remove(beanName); // 删除二级缓存中对应的bean this.earlySingletonObjects.remove(beanName); // 记录当前所有已注册的bean this.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解决循环依赖的详细过程,这张图是笔者跟踪循环依赖处理流程时画的,图中涉及细节比较多,建议读者对照着源码一点点看图,相信多看几遍就能理清其中的处理逻辑了。
希望本篇文章对大家有所帮助,最后也希望读者能指出文章不对之处。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)