如何解决Spring中的循环依赖问题

目录

一、什么是循环依赖


一、什么是循环依赖

循环依赖是指类与类之间的依赖关系形成了闭环,例如类A依赖类B,类B也依赖类A,这种情况就会出现循环依赖。

BeanA \rightarrow BeanB \rightarrow BeanA

当Spring上下文在加载所有的bean时,会尝试按照他们关联关系的顺序进行创建。如果不存在循环依赖时,例如:

BeanA \rightarrow BeanB \rightarrow BeanC

  • 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来;
  • 接着 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来;
  • 于是 C 就走创建流程,实例化完成后将完整的实例化对象 C 传给 B 完成 B 的实例化,接着 B 将一个完整的实例化对象 B 传给 A 完成 A 的 实例化。

但是,如果存在循环依赖,这时Spring的上下文就不知道应该先创建哪个Bean,因为它们之间彼此互相依赖。

在这种情况下,Spring会在加载上下文时,抛出一个BeanCurrentlyInCreationException异常。
当使用构造方法进行注入时,就会遇到这种情况,因为它在上下文加载时就被要求注入。

定义类UserA

@Component
public class UserA {
    private UserB b;

    @Autowired
    public UserA(UserB b) {
        this.b = b;
    }

    public UserA() {
    }

}

定义类UserB 

@Component
public class UserB {
    private UserA a;

    @Autowired
    public UserB(UserA a) {
        this.a = a;
    }

    public UserB() {
    }
}

配置类 

@Configuration
@ComponentScan(basePackages = { "com.ape" })
public class Config {

}

测试类 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Config.class})
public class Test01 {

    @Test
    public void show1(){

    }

}

此时运行就会出现BeanCurrentlyInCreationException异常。


二、解决方法

由测试可知,构造方法是在上下文加载时就要求被注入,因此容易出现依赖循环。所以可以用其他的方式进行依赖注入,setter 和 field 方法注入与构造方法不同,它们不会在创建Bean时就注入依赖,而是在被需要时才注入

1、设计Bean思路

1、setter注入

@Component
public class UserA {
    private UserB b;

    public UserA(UserB b) {
        this.b = b;
    }

    @Autowired
    public void setB(UserB b) {
        this.b = b;
    }

    public UserA() {
    }

}
@Component
public class UserB {
    private UserA a;

    public UserB(UserA a) {
        this.a = a;
    }

    @Autowired
    public void setA(UserA a) {
        this.a = a;
    }

    public UserB() {
    }
}

此时运行就不会产生异常,即不存在循环依赖问题


2、field注入

@Component
public class UserA {
    @Autowired
    private UserB b;

}
@Component
public class UserB {
    @Autowired
    private UserA a;

}

此时运行也不会产生异常,即不存在循环依赖问题


3、懒加载

解决Spring 循环依赖还可以对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上注入的是一个代理,只有当首次被使用的时候才会被完全的初始化。

@Component
public class UserA {

    private UserB b;

    @Autowired
    public UserA(@Lazy UserB b) {
        this.b = b;
    }

    public void setB(UserB b) {
        this.b = b;
    }

    public UserA() {
    }

}
@Component
public class UserB {

    private UserA a;

    @Autowired
    public UserB(@Lazy UserA a) {
        this.a = a;
    }

    public void setA(UserA a) {
        this.a = a;
    }

    public UserB() {
    }
}

此时运行同样不会产生异常,即不存在循环依赖问题


2、底层逻辑

Spring使用三级缓存来解决单例模式下的循环依赖问题。

Spring 内部维护了三个 Map,也就是我们通常说的三级缓存,可以注意到三级缓存与前两级缓存不太一样,Map 中维护的值是 ObjectFactory 类型。

//单例缓存池 beanName - instance 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//bean的早期引用, bean name to bean instance 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

//单例工厂 beanName - ObjectFactory  三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • singletonObjects:一级缓存,一个单例bean【实例化+初始化】都完成之后,将会加入一级缓存,也就是我们俗称的单例池
  • earlySingletonObjects:二级缓存,用于存放【实例化完成,还没初始化】的实例,提前暴露,用于解决循环依赖问题
  • singletonFactories:三级缓存,存放单例对象工厂ObjectFactory,与二级缓存不同的是,它可以应对产生代理对象

其中,后两个Map是临时使用的,只在创建 Bean 的时候用来借助一下,创建完成就清除。


下述为类A和类B进行创建时的流程

其中singletonsCurrentlyInCreation表示当前正在创建的bean(仅存放名字 beanName)。

1、初始阶段

2、实例化A

3、A 实例化完成,此时将 A 封装成 ObjectFactory 对象,但是属性还没有赋值(即B没有进行赋值),我们先定义名称为 aObject

4、给 A 的属性赋值,也就是给 B 赋值,此时要去创建 B,创建 B 的过程和创建 A 过程是一样的,依次从一级缓存、二级缓存、三级缓存中获取 B。此时很明显是获取不到的,那么就要创建 B,在singletonsCurrentlyInCreation 中加入 B,表示当前 B 正在创建。

5、B 实例化完成,但是属性还没有赋值,此时将 B 封装成 ObjectFactory 对象放入三级缓存,先定义名称为 BObject

6、此时开始给 B 的属性赋值,也就是给 A 赋值,那么就要去创建 A,创建 A 的过程和之前的一样,依次从一级、二级、三级缓存中获取 A,由于此时可以从三级缓存中获取到 A ,因此将获取到的 A 赋值给 B ,此时 B 即将创建完成,在全部创建完的前一步,将三级缓存中的 B 移到二级缓存,此时实例化 B 的全部步骤全部完成。

7、B 实例化以后,需要从当前正在创建的容器(singletonsCurrentInCreation)中移除 B,这步表示 B 已经创建完成了,并同时将 B 移到一级缓存 singletonObjects 中。

8、开始对 A 进行属性赋值

9、A 创建完成存入一级缓存

三、总结


在Spring中,有很多方法可以处理循环依赖关系。首先要考虑的是重新设计bean,这样就不需要循环依赖关系,如果是在单例模式下,那么可以采取三级缓存来解决循环依赖问题。

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值