Spring循环依赖原理

Spring循环依赖原理,第1张

Spring循环依赖原理

目录

一、什么是循环依赖?

二、构造器参数循环依赖

三、单例的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异常。

不过,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:


    



    

同样运行测试类,观察控制台输出: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的三个重要的步骤:

  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 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 Map singletonObjects = 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解决循环依赖的详细过程,这张图是笔者跟踪循环依赖处理流程时画的,图中涉及细节比较多,建议读者对照着源码一点点看图,相信多看几遍就能理清其中的处理逻辑了。

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5685459.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存