【SpringBoot】SpringBoot和循环依赖

一句话说清楚

(1)什么是循环依赖?

循环依赖就是A->B->A,spring在创建A的时候,发现需要依赖B,因为去创建B实例,发现B又依赖于A,又去创建A,因为形成一个闭环,无法停止下来就可能会导致cpu计算飙升

(2)Spring 如何解决循环依赖的?

spring内部通过3级缓存来解决循环依赖(所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map),Spring解决循环依赖依靠的是通过缓存一个Bean的“中间态",,这个中间态指的是已经实例化但还没初始化的状态——半成品(二级缓存),此方法解决的是单例的bean注入(set||注解 方式)

注意:

  1. 三级缓存的实例化的过程又是通过构造器创建的,如果A还没创建出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
  2. 只有单例bean会通过三级缓存提前暴露来解决循环依赖问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean没有缓存,也不会将其放入三级缓存中。

1. 案例分析

1.1 循坏依赖场景

(1)用户服务中引用商品服务

public class UserService {
    private GoodsService goodsService;
}

(2)商品服务中引用用户服务

public class GoodsService {
    private UserService userService;
}

两者相互依赖,就像下图所示:

                            

1.2 探究如何 getBean

Object ApplicationContext.getBean(Class cls) 是我们在项目中常用的获取 Bean 对象的方法,其本质是调用 beanFactory.getBean(),而 beanFactory 的常用对象就是 DefaultListableBeanFactory类

(1)下面我们就来看一下怎么用 DefaultListableBeanFactory 来获取 Bean 对象。

出于对 getBean() 方法的熟悉,我们“熟练”地写出了下面的代码:

//  注意:这是一个会报错的常见错误示例 
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

UserService userService = beanFactory.getBean(UserService.class);

遗憾的是,代码运行时抛出了 NoSuchBeanDefinitionException,这个时候我注意到 BeanDefinitionRegistry 接口,这是 DefaultListableBeanFactory 实现的一个接口。

这个接口提供了注册 BeanDefinition 的方法

void registerBeanDefinition(String name, BeanDefinition beanDefinition) // 添加bean,为工厂类注册

于是我们改写代码:

  // 这个例子中 userService 对象属性 goodsService 为 null,不算正确
    @Test
    public void createBeanTest() {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        RootBeanDefinition def = new RootBeanDefinition(UserService.class);// 添加 BeanDefinition
        factory.registerBeanDefinition("userService", def); // 为工厂类注册 BeanDefinition
        UserService bean = factory.getBean(UserService.class);
        System.out.println(bean);
    }

我这次用 debug 模式,断点断在最后一行的输出语句上,然后 Evaluate 得到如下图的结果:

    

很不幸,这次没能正常填充属性 goodsService,于是我们再次调用 

RootBeanDefinition.setPropertyValues(MutablePropertyValues propValues)  // 设置属性方法

来改写代码:

// 还是一段错误的代码示例
@Test
public void createBeanTest() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    RootBeanDefinition def = new RootBeanDefinition(UserService.class);
    // 新增代码:填充属性 goodsService
    def.setPropertyValues(new MutablePropertyValues()
                        .add("goodsService", new RuntimeBeanReference(GoodsService.class)));
    factory.registerBeanDefinition("userService", def);
    UserService bean = factory.getBean(UserService.class);
    System.out.println(bean);
}

控制台情况下:

如图所示,这次抛出了 BeanCreationException,该异常发生在 UserService 填充属性 goodsService 的时候。嵌套的异常还是 NoSuchBeanDefinitionException,主要原因是没有 GoodsService 对应的 BeanDefinition,我们又又又改写一次:

// 这是一段正确的代码
@Test
public void test() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    {
        RootBeanDefinition userServiceDef = new RootBeanDefinition(UserService.class);
        userServiceDef.setPropertyValues(new MutablePropertyValues()
                .add("goodsService", new RuntimeBeanReference("goodsService")));
        factory.registerBeanDefinition("userService", userServiceDef);
    }
    {
        RootBeanDefinition goodsServiceDef = new RootBeanDefinition(); // 为商品类GoodsService补充定义
        goodsServiceDef.setBeanClass(GoodsService.class);
        goodsServiceDef.setPropertyValues(new MutablePropertyValues()
                .add("userService", new RuntimeBeanReference("userService")));
        factory.registerBeanDefinition("goodsService", goodsServiceDef);
    }
    factory.getBean(UserService.class);
}

如图所示,再抛异常:

错误原因:缺少 setter 方法,我们需要修正一下 UserService 和 GoodsService 类,给他们增加 setter 方法,修正如下:

public class UserService {
    private GoodsService goodsService;
    // 修正:增加 setter 方法
    public void setGoodsService(GoodsService goodsService) {
        this.goodsService = goodsService;
    }
}
public class GoodsService {
    private UserService userService;
    // 修正:增加 setter 方法
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

我们再次debug,把断点打在最后一行,然后 Evaluate。我们最终得到了期望结果——“无限套娃”(即循环依赖):

2. Bean的创建过程

经过上面一系列的操作,想必大家已经看出了一些端倪,创建 Bean 好像是下面这个过程:

       

  1.  getSingleton:希望从容器里面获得单例的bean,没有的话
  2.  doCreateBean: 没有就创建bean
  3.  populateBean: 创建完了以后,要填充属性
  4.  addSingleton: 填充完了以后,再添加到容器进行使用

   

上面这张图结合了我们先前探究的过程,我们有了一个初步的印象,Bean 的创建过程似乎可以简化为如下过程:

  • 实例化 Bean,简单理解就是 UserService bean = new UserService()
  • 填充属性,即 bean.setGoodsService(new GoodsService())
  • 后置代理,就是在 Bean 外面再包装一层 BeanProxy proxy = new BeanProxy(bean); proxy.doSomething();
  • 添加到实例池: Map<String, Object> instances = new HashMap<>(); instances.put("userService", bean);

三大Map的作用:

  • 第一层singletonObjects存放的是已经初始化好了的Bean,
  • 第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
  • 第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

A/B两对象在三级缓存中的迁移说明

  • A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
  • B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A 
  • B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
  • 然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

Spring解决循环依赖过程,方法说明:

  1. 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
  2. 在getSingleton()方法中,从一级缓存中查找,没有,返回null
  3. doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
  4. 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
  5. 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
  6. 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
  7. 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
  8. 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
  9. 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
  10.  随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

2.1 Spring 创建 Bean 的简化流程

首先我们来看一下 DefaultListableBeanFactory 类继承结构图:

Spring 创建Bean的简化过程如下图所示:

  1. 首先调用公开方法 DefaultListableBeanFactory.getBean(Class cls) 来获取 Bean 对象,通过一系列操作转化 class 对象为 beanName。
  2. 得到 beanName 之后,调用 AbstractBeanFactory.getBean(String name, Class cls) , 在这个方法中又会调用受保护的 AbstractBeanFactory.doGetBean 方法
  3. 在 doGetBean 方法中,又会调用 DefaultSingletonBeanFactory.getSingleton(String beanName, ObjectFactory factory)
  4. 在 getSingleton 方法中,首先在单例池 DefaultSingletonBeanFactory.singletonObjects 查找是否已经存在 beanName 对应的单例对象。如果找不到,那会调用函数式接口 ObjectFactory 来创建一个对象,而这个对象工厂调用的函数是一个受保护的抽象方法 AbstractBeanFactory.crateBean(String beanName, RootBeanDefinition def, Object[] args)
  5. AbstractAutowireCapableBeanFactory 实现了超类的 createBean 方法, 并在 createBean 方法中调用了受保护的方法 AbstractAutowireCapableBeanFactory.doCreateBean
  6. 在 doCreateBean方法中,还会调用 AbstractAutowireCapableBeanFactory.populateBean 来填充属性。
  7. 在 populateBean 填充属性时,还可能遇上没有创建过的对象,因此还可能递归调用 AbstractBeanFactory.getBean(String beanName)

小结一下:

  • AbstractAutowireCapableBeanFactory 负责了实例化 Bean,填充属性,后置处理的任务
  • DefaultSingletonBeanRegistry 负责了单例池的维护工作。

2.2 Spring 解决循环依赖

解决循环依赖,关键还得看注册中心中三个方法:添加单例工厂、获取未完成的单例以及添加到单例池。首先我们需要了解一下注册中心三个重要的成员变量:

             

我们把断点打在 DefaultSingletonBeanRegistry.getSingleton 方法内:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = 
                                      (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }

        return singletonObject;
    }

(1)第一次调用发生 doGetBean 中,查询单例池中是否已经存在 UserService bean 对象,

       即调用 getSingleton("userService" /*beanName*/, true /*allowEarlyReference*/),方法返回 null。

      

  • 没有查询到单例,因此先调用 DefaultSingletonBeanRegistry.beforeSingletonCreation("userService" /*beanName*/),调用后正在创建的单例对象新增了 "userService"

     

  • 然后调用 getSingleton("userService" /*beanName*/, ObjectFactory<?> singletonFactory) 获取并创建 UserService bean 对象。
  • 紧接着调用 doCreateBean 方法,并在该方法中调用 addSingletonFactory("userService" /*beanName*/, ObjectFactory<?> singletonFactory) ,需要警惕的是,这里的 ObjectFactory 匿名类调用的方法是 AbstractAutowireBeanFactory.getEarlyBeanReference,通过该工厂创建的是未完成的 bean 对象。

        

  • 再接着就该调用 populateBean 方法,并且在该方法中调用 applyPropertyValues ,欲为 UserService bean 对象设置 goodsService 属性
  • 再次调用 doGetBean,查询单例池中是否已经存在 GoodsService bean 对象,即调用 getSingleton("goodsService" /*beanName*/, true /*allowEarlyReference*/),方法返回 null。
  • 执行 DefaultSingletonBeanRegistry.beforeSingletonCreation("goodsService" /*beanName*/) 后,正在创建的单例对象新增了 "goodsService"

    

  • 接着就该获取和创建 GoodsService bean 对象了,和创建 UserService bean 对象类似,在调用 addSingletonFactory 方法后,单例工厂集发生了变化,注册单例集也发生了变化:

     

  • 在为 GoodsService bean 对象设置 userService 属性时,再一次进入了 getSingleton("userService" /*beanName*/, true /*allowEarlyReference*/),但是这次,从单例工厂集合中取出了 "userService" 对应的 ObjectFactory 对象,创建了一个 未完成的UserService Bean对象,放到了未完成单例集中,同时从单例工厂中移除了"userService"对应的 ObjectFactory 工厂。

     

  • 紧接着 GoodsService Bean 对象先调用了 afterSingletonCreation("goodsService" /*beanName*/),移出了正在创建单例集
  • 然后就移除工厂集中的 ObjectFactory 对象,并将 GoodsService Bean 放入单例池:
  • 然后 UserService Bean 对象也成功设置了 goodsService 属性,完成创建,紧接着调用 afterSingletonCreation 和 addSingleton 方法

2.3 spring如何解决循环依赖问题

(1)循环依赖问题

循环依赖就是A->B->A,spring在创建A的时候,发现需要依赖B,因为去创建B实例,发现B又依赖于A,又去创建A,因为形成一个闭环,无法停止下来就可能会导致cpu计算飙升

(2)如何解决这个问题呢?

Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化

  • 每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个,当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程,
  • 不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建,既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成

spring解决这个问题主要靠巧妙的三层缓存,所谓的缓存主要是指这三个map:

  • singletonObjects主要存放的是单例对象,属于第一级缓存;
  • singletonFactories属于单例工厂对象,属于第三级缓存;
  • earlySingletonObjects属于第二级缓存

如何理解early这个标识呢?它表示只是经过了实例化尚未初始化的对象。

Spring解决循环依赖依靠的是Bean的“中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态……>半成品

  • 实例化的过程又是通过构造器创建的,如果A还没创建出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

Spring为了解决单例的循环依赖问题,使用了三级缓存其中:

  • 一级缓存为单例池〈 singletonObjects)
  • 二级缓存为提前曝光对象( earlySingletonObjects)
  • 三级缓存为提前曝光对象工厂( singletonFactories)。

假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,

  • 没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,
  • 如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。

总结,Spring 就是靠获取未完成的Bean对象来填充属性,解决循环依赖的。

2.4 循环依赖常见问题

(1)为什么不直接把对象放入 earlySingletonObjects,而是先放入 singletonFactories 中?

  • 代理对象,不能填充属性

创建对象是需要消耗性能的,假如创建没有循环依赖的对象,是不需要创建未完成的Bean对象的,所以不是直接创建未完成对象放入未完成单例集 earlySingletonObjects,而是把未完成单例工厂放入 singletonFactories
比如以下这段代码,在运行时就不会调用 getSingleton 中的 ObjectFactory.getObject() 方法来创建 TestService 未完成 Bean 对象。

// Bean 对象类
public class TestService {
    String name;

    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

//==============================
public class CreateBeanTest {
    @Test
    public void testCreateTestService() {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        RootBeanDefinition definition = new RootBeanDefinition(TestService.class);
        definition.setPropertyValues(new MutablePropertyValues().add("name", "admin"));
        factory.registerBeanDefinition("testService", definition);
        TestService service = factory.getBean(TestService.class);
        System.out.println(service.getName());
    }
}

(2)超类是如何调用子类的方法的?

  • 超类 DefaultSingletonBeanRegistry 是如何调用子类 AbstractAutowireCapableBeanFactory 中的 doGetBean 方法的?
// AbstractBeanFactroy.doGetBean 方法中的一段代码
sharedInstance = this.getSingleton(beanName, () -> {
    try {
        return this.createBean(beanName, mbd, args);
    } catch (BeansException exp) {
        this.destroySingleton(beanName);
        throw exp;
    }
});

对于 AbstractBeanFactroy 而言, createBean 是一个需要子类实现的抽象方法

// AbstractBeanFactory 类中的抽象方法 createBean
protected abstract Object createBean(String var1, 
                                    RootBeanDefinition var2, 
                                    @Nullable Object[] var3) throws BeanCreationException;

然后我们来看 DefaultSingletonBeanRegistry 获取并创建单例的方法

// 节选下面方法中一段代码
 DefaultSingletonBeanRegistry.getSingleton(String beanName, 
                                                 ObjectFactory<?> singletonFactory)  
try {
    singletonObject = singletonFactory.getObject();
    newSingleton = true;
} catch (IllegalStateException exp1) {
    // ... (省略)
} catch (BeanCreationException exp2) {
    // ... (省略)
}

因此 AbstractBeanFactroy 通过 ObjectFactory.getObject() 间接调用了 createBean 方法,而子类 AbstractAutowireCapableBeanFactory实现了 createBean 方法

// AbstractAutowireCapableBeanFactory.createBean 方法中的一段代码
beanInstance = this.doCreateBean(beanName, mbdToUse, args);

小结一下:
超类 DefaultSingletonBeanRegistry 是在函数式接口 ObjectFactory 的匿名实现类中,调用抽象方法 createBean
再由子类 AbstractAutowireCapableBeanFactory 实现抽象方法,来达到超类调用子类方法的目的。

(3)  为什么是三级缓存,二级行不行?

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

  1. 如何利用“三级缓存“巧妙解决Bean的循环依赖问题 [为什么是三级缓存,二级行不行?:代理类]
  2. Spring 如何解决循环依赖的问题 导致初始化失败【构造函数方式在三级缓存初始化Bean,所以解决不了,属性注入提前暴露从而完成初始化】

相关文章

  1. Spring是如何解决循环依赖的?
  2. spring加载bean流程解析
  3. Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

试剑江湖。

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

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

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

打赏作者

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

抵扣说明:

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

余额充值