【Spring成神之路】从源码角度深度刨析Spring循环依赖

一、引言

循环依赖,顾名思义就是两个Bean互相依赖导致出现的问题,我个人感觉有点死锁的味道,就是A需要实例化的B才能完成实例化,而B而又需要实例化的A才能完成实例化,从而出现了循环依赖问题。

那么Spring是如何解决循环依赖问题的呢?相信这个问题的答案大家随口就能回答,但是我觉得知道理论的回答还不够,我认为还得亲自跟着源码看一遍Spring到底是如何解决的才会印象深刻。

本篇文章会从理论和源码入手,先讲解循环依赖解决的理论部分,后面直接对源码DEBUG,深入理解循环依赖问题。

PS:本篇文章中使用的Spring框架的版本是4.0.0.RELEASE,不同版本之间源码会有一点点不同,但是大体逻辑差不多。

另外,阅读本篇文章之前最好对Spring IOCSpring AOP的实现有一定了解,如果不太了解这两部分可以阅读一下两篇文章:

  1. 【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!
  2. 【Spring成神之路】老兄,来一杯Spring AOP源码吗?

二、循环依赖出现的场景

循环依赖的出现有两种场景,第一种是有参构造导致的循环依赖问题,第二种是属性注入导致的循环依赖问题,请看下面举的栗子


2.1 有参构造导致的循环依赖问题

先准备两个Bean,两个相互依赖

public class BeanA {
    BeanB beanB;
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}
public class BeanB {
    BeanA beanA;
    public BeanB(BeanA beanA){
        this.beanA = beanA;
    }
}

配置文件中Bean的注入使用构造器注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="beanA" class="spring.loop.BeanA">
        <constructor-arg ref="beanB"/>
    </bean>

    <bean id="beanB" class="spring.loop.BeanB">
        <constructor-arg ref="beanA"/>
    </bean>

</beans>

测试类

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("application-loop.xml");
    BeanA beanA = context.getBean(BeanA.class);
    System.out.println(beanA);
}

运行代码

image-20240711223325531

不出所料,Spring框架报错,提示当前请求的Bean真正创建中,是否存在循环依赖


2.2 属性注入出现的依赖问题

改用set方法进行属性注入

public class BeanA {
    BeanB beanB;
    public BeanB getBeanB() {
        return beanB;
    }
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}
public class BeanB {
    BeanA beanA;
    public BeanA getBeanA() {
        return beanA;
    }
    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="beanA" class="spring.loop.BeanA">
        <property name="beanB" ref="beanB"/>
    </bean>

    <bean id="beanB" class="spring.loop.BeanB">
        <property name="beanA" ref="beanA"/>
    </bean>

</beans>

测试类

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-loop.xml");
        BeanA beanA = context.getBean(BeanA.class);
        BeanB beanB = context.getBean(BeanB.class);
        System.out.println(beanA);
        System.out.println(beanA.beanB);
        System.out.println(beanB);
        System.out.println(beanB.beanA);
    }
}

image-20240711223850124

运行代码发现,欸?!居然没报错?循环依赖居然成功完成了属性注入!!这到底发生了什么?

别急,看我后面慢慢道来。。。。。


2.3 Spring IOC创建Bean的流程

首先需要先大概了解Spring IOC创建Bean的流程,这里不会涉及源码,如果对源码感兴趣,请看这篇文章【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!

在Spring框架中,Bean的创建流程如下图所示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 获取构造方法,并通过构造方法创建实例
  2. 实例化之后进行属性注入
  3. 执行初始化方法,包括前置通知、后置通知等
  4. 最后完成实例化

2.4 有参构造为何失败

image-20240711230046556

由上图可见,调用BeanA的有参构造方法实例化的时候需要BeanB的实例,而实例化BeanB则需要BeanA的实例,两者形成了一个循环依赖,导致无法创建实例。

这个就好比老板需要你把这BUG改完才给你发工资,而你又要老板想给你发工资再改这个BUG,大家都不肯让步。


2.5 属性注入为何能成功

**那属性注入的方式,为何能解决循环依赖呢?**别急,同样的看下图

image-20240711231852822

这里就比较巧妙了,就BeanA来说,创建BeanA的时候,会先调用BeanA的无参构造,实例化一个未完成属性注入的BeanA实例,这时候将这个半成品BeanA放进缓存(earlySingletonObjects,这种缓存也称为提前暴露缓存)中去。

然后BeanA进行属性注入,属性注入需要BeanB的实例,然后开始实例化BeanB,调用BeanB的无参构造方法,实例化了一个未完成属性注入的BeanB,并将其放进缓存中去,BeanB在属性注入阶段需要BeanA的实例,于是会从缓存中获取BeanA实例完成属性注入,接着执行处理初始化方法完成BeanB的实例化。

有了BeanB的实例后,BeanA的创建自然就畅通无阻啦!

这时候就有大聪明就问了,在有参构造的时候我也弄个缓存不行么?

欸?还真不行喔,就上面这个有参构造例子来说,BeanA并不存在半成品之说,要想创建一个BeanA,就必须有一个BeanB的实例才可以,而创建BeanB又需要一个BeanA的实例。

而属性注入这个例子中,BeanABeanB都可以通过无参构造先创建一个实例,无参构造就意味着实例化一个半成品的BeanABeanB的时候不存在任何依赖(当然也不是一定要无参构造,有参构造只要参数不存在循环依赖即可),然后放进缓存供给BeanABeanB完成属性注入即可,这样循环依赖问题自然就迎刃而解啦


2.6 AOP导致的循环依赖

解决Spring的循环依赖问题,需要三级缓存:

  1. 一级缓存(singletonObject:所有创建好的单例Bean
  2. 二级缓存(earlySingletonObjects:完成实例化但是未进行属性注入以及初始化的Bean实例
  3. 三级缓存(singletonFactories:提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂获取的对象

一级缓存、二级缓存很好理解,三级缓存到底是为了解决什么场景的问题的呢?

试想一下,如果我们创建的对象是一个代理对象,只用一级缓存和二级缓存能够解决循环依赖问题呢?

答案是不会出现循环依赖问题,但是创建出来的对象是不正确的!!!不正确的!!!不正确的!!!

image-20240715204419880

假设BeanA是经过AOP代理的,BeanB则是正常的Bean,BeanA的实例创建好之后会放进缓存池中,接着创建BeanBBeanB的创建完实例之后,进行属性注入,从缓存池中获取BeanA的实例,将缓存池中BeanA的引用赋值给BeanB实例的beanA成员变量

BeanB实例化后,BeanB的实例引用会赋值给BeanA的实例,完成属性注入后,BeanA会进行AOP代理创建出代理对象,并将IOC容器中的beanA的引用指向经过AOP代理后生成的BeanA实例,也就是代理实例。

可见,在没有三级缓存的情况下,BeanB实例中的beanA成员变量的引用是beanA的实例,而不是其代理类的实例,于是就会出现奇奇怪怪的问题。

那怎么解决这个问题呢?

所以三级缓存的存在就是为了解决AOP代理,具体解决的AOP代理的循环依赖的流程如下图所示

image-20240715231658491

区别就在于BeanB的属性注入这里,如果BeanA的实例化是一个AOP代理,则获取其代理对象,否则获取其半实例化对象。

当然,Spring的实现细节和这个图有所区别,但是大体思路差不多


三、Spring循环依赖源码刨析

PS:下面的源码解读会比较乱,就是看起来会感觉联系不起来,但是没关系可以先看一个眼熟,后面看我的流程图还有结合案例就能理解了

直接定位org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean这个方法

protected <T> T doGetBean(
    String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    throws BeansException {
    String beanName = transformedBeanName(name);
    Object bean;

    // 查询缓存中是否存在这个bean
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null){
        // 省略部分代码
    }else{
        // 缓存中不存在,获取BeanDefinition
        RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        // 检查是否是单例
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }
}

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中,主要有两个流程:

  1. 从缓存中获取Bean
  2. 若缓存没有命中,则创建这个Bean

把目光集中在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)方法中去

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存中获取Bean实例
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 一级缓存不存在这个Bean,且当前Bean处于创建中,于是从二级缓存中获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 二级缓存中不存在这个Bean,获取锁,再从一二三级缓存中尝试获取实例
            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<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 创建实例
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

这个方法的逻辑很简单,就是从一级、二级、三级缓存中获取Bean实例。在getSingleton返回的不为null,则经过一些处理后就会从doGetBean返回。

如果从缓存中获取不到,会走org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)方法

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
       Object singletonObject = this.singletonObjects.get(beanName);
       if (singletonObject == null) {
           // 一级缓存不存在该Bean
          beforeSingletonCreation(beanName);
          boolean newSingleton = false;
           // 创建单例Bean
           singletonObject = singletonFactory.getObject();
           newSingleton = true;
          // 如果是一个单例则放进一级缓存
          if (newSingleton) {
             addSingleton(beanName, singletonObject);
          }
       }
       return singletonObject;
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 将bean放进一级缓存 二级缓存移除该bean
        this.singletonObjects.put(beanName, singletonObject);
        // 三级缓存移除该Bean
        this.singletonFactories.remove(beanName);
        // 二级缓存移除该Bean
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

getSingleton方法的主要作用是从一级缓存中获取Bean,如果Bean不存在则创建缓存

singletonFactory.getObject()实际调用的就是org.springframework.beans.factory.support.AbstractBeanFactory#createBean方法

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
       throws BeanCreationException {

    RootBeanDefinition mbdToUse = mbd;
    // 创建目标实例
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    return beanInstance;

}

先来看创建目标实例的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法吧

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
       throws BeanCreationException {
    // 创建实例
    Object bean = instanceWrapper.getWrappedInstance();
	// 检查是否需要提前暴露这个Bean
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
		// 如果需要提前暴露,则放进三级缓存中去
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
}

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 三级缓存中存储Bean
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

然后再看看org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    // 检查Bean是否被动态代理,如果被动态代理需要返回代理对象
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
       for (BeanPostProcessor bp : getBeanPostProcessors()) {
          if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
             SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
              // 创建代理对象
             exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
          }
       }
    }
    return exposedObject;
}

// 生成代理对象
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 放进二级缓存
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
}

四、Spring循环依赖案例刨析

PS:这一块需要结合第三部分的源码刨析以及以下流程图进行配合使用,否则会看得稀里糊涂!

就前面BeanABeanB循环依赖的案例来说,下面会一步一步刨析循环依赖的三级缓存是如何操作的。

image-20240715204419880

SpringIOC刷新的时候,会调用org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons进行预处理单例Bean。

假设先初始化BeanA,会调用org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean进行创建Bean。

一开始会先从调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)从三级缓存中进行获取BeanA实例,这时候BeanA实例肯定是不存在的。

接着调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)方法。

在这个方法中会调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])进行创建BeanA实例。

实例创建完成后,会将这个BeanA实例的工厂方法getEarlyBeanReference添加进三级缓存。

getEarlyBeanReference方法是一个获取代理对象的方法,如果前面实例化的BeanA是代理对象,则会将这个对象进行动态代理,返回其代理对象,否则返回前面实例化的BeanA实例。

这时候,三级缓存如下所示

image-20240803150754210

然后BeanA进行属性注入,尝试从SpringIOC中获取BeanB

同理BeanB也会这样被放进第三级缓存

image-20240803153356979

BeanB实例化完成后,会进行属性注入,从缓存中获取BeanA实例

当从缓存中获取BeanA实例的时候,会先执行org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)方法,这时候第三级的缓存始有BeanA的,会拿到BeanA工厂Bean,并调用其getObject方法,也就是前面缓存的getEarlyBeanReference方法获取BeanA的半实例化对象,并放到二级缓存中去。

image-20240803153923386

BeanB获取道BeanA未完成的实例进行完成属性填充后,会调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton

BeanB在二三级的缓存移除,并将BeanB的实例缓存进一级缓存。

image-20240803155550496

这时候,BeanA的实例化就能获取完成实例化的BeanB进行输入注入,并缓存进一级缓存!

image-20240803155732526

Spring循环依赖的整体流程就是这样子了!


  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起名方面没有灵感

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值