深入了解Spring循环依赖和三级缓存机制

在Spring框架中,循环依赖是指多个bean之间相互依赖,可能会导致在实例化过程中的死锁或无限递归。为了解决这个问题,Spring引入了三级缓存机制,用于存储在创建单例bean实例的过程中的相关数据。本篇博客将深入探讨Spring循环依赖和三级缓存机制,包括循环依赖的生成流程和每层缓存的作用。

1. 循环依赖的生成流程

当两个或多个bean相互依赖时,可能会导致循环依赖问题。为了更好地理解循环依赖的生成流程,我们可以通过以下步骤来说明:

  • 应用程序启动,Spring容器开始初始化。
  • 当创建bean A时,发现需要依赖于bean B。
  • 然后,创建bean B时,又发现需要依赖于bean A。
  • 此时就形成了循环依赖,Spring容器需要解决这一依赖关系。

2. 三级缓存的作用

Spring的三级缓存包括singletonObjects、earlySingletonObjects和singletonFactories。它们的作用分别如下:

  • singletonObjects(一级缓存): 存储已经实例化的单例bean对象。
  • earlySingletonObjects(二级缓存): 存储早期暴露的bean实例,用于解决循环依赖。
  • singletonFactories(三级缓存): 存储用于创建bean实例的ObjectFactory。

大白话:
一级缓存:存储最终的单例bean,可能是原始bean对象,也可能是代理bean对象(区别在于是否需要生成代理类,比如AOP)。
二级缓存:存储提前暴露的单例bean,比如创建A的时候发现需要依赖B,但是B在一级缓存中没有,就需要创建B,创建B的时候又依赖A这时候就需要提前创建一个A放入二级缓存,提供给B使用,从而终止循环完成了B的创建。
三级缓存:用于存放bean工厂的lambda表达式,二级缓存中的代理对象就是通过lambda表达式创建的。

3. 举例说明

假设我们有两个类A和B,它们相互依赖,可以通过以下代码来说明循环依赖和三级缓存的作用:

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;
    }
}

在这个例子中,类A和B相互依赖,创建其中一个类的实例时,需要引用另一个类的实例。这时,Spring会使用三级缓存来解决循环依赖问题,具体流程如下:

首先创建bean主要分三步:实例化(new一个对象)、属性赋值(对象赋值),初始化(最终bean对象)

  1. 实例化A对象,并放入createSet中,并生成lambda表达式放入三级缓存。
  2. 给A对象赋值时发现依赖于B,但是从一级缓存和二级缓存都没有找到bean。于是会创建一个B对象,刚开始是实例化B对象,然后接着给B对象赋值,此时发现B依赖于A。但是从一二级缓存都没办法找到A对象bean,同时发现createSet中有A对象(于是知道A对象在创建中),便根据三级缓存中的lambda表达式创建一个提前暴露的A对象bean放到二级缓存(创建的是否为代理对象也是这一步完成),同时把三级缓存中A的lambda表达式清除。然后B对象就可以完成属性赋值。从而完成后面的初始化操作把B对象放入一级缓存。
  3. 上面完成了B的初始化,就可以完成A对象的属性赋值,同时把A在二级缓存的提前暴露bean移到一级缓存。

4. 其他

4.1 三级缓存无法解决的循环依赖场景

如下通过构造函数创建注入的bean无法通过三级缓存解决,原因是这创建bean的第一步实例化时就会调用构造方法。因为默认调用的是无参构造函数,但是由于这里声明了有参构造函数所以调用的时候发现BService 为null,就无法完成A的实例化,因此在解决循环依赖时就无法生成A的提前暴露bean,因此无法解决循环依赖。

@Service
public class AServiceImpl implements AService{

    private BService bService;

    public AServiceImpl(BService bService) {
        this.bService = bService;
    }

    @Override
    public void a() {
        System.out.println("hello 方法A");
        bService.b();
    }
}

解决方法:

  1. 改为非构造函数注入bean。
  2. 加上 @Lazy注解
@Service
public class AServiceImpl implements AService{

    private BService bService;

    @Lazy
    public AServiceImpl(BService bService) {
        this.bService = bService;
    }

    @Override
    public void a() {
        System.out.println("hello 方法A");
        bService.b();
    }
}

为什么@Lazy 注解可以解决循环依赖?因为@Lazy注解会生成一个BService 代理对象,然后A就可以顺利完成实例化,从而解决循环依赖问题。

  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值