Spring框架中的循环依赖以及优化方案

一、对于循环依赖概述:

1、循环依赖:

      简单来说:循环依赖是指一个Bean或者多个Bean实例之间,会存在直接或者间接的一个依赖关系,构成一个循环调用,也称为循环引用。

2.其形态主要分为以下三点:

       ① 互相依赖

//A注入了B
@Component
public class BeanA{
@Autowired
BeanB b;
}

//B也注入了A
@Component
public class BeanB{
@Autowired
BeanA a;
}

           ②  间接依赖

//注入B
@Component
public class BeanA{
@Autowired
BeanB b;
}

@Component
public class BeanB{
@Autowired
BeanA a;
@Autowired
BeanC c;
}

@Component
public class BeanC{
@Autowired
BeanA a;
}

     ②  自我依赖     

@Component
public class BeanA{
@Autowired
BeanA a;
}

3、循环依赖的问题

在Spring框架中,循环依赖可能导致以下问题:

      3.1. 实例化困难:如果两个或多个Bean相互依赖,Spring容器在尝试实例化这些Bean时可能会遇到困难,因为它需要按照一定的顺序来解决依赖关系。

      3.2. 初始化失败:在Bean的初始化过程中,如果发现循环依赖,可能会导致属性赋值出错,进而引发初始化失败。

     3.3. 破坏设计原则:循环依赖违反了面向对象设计中的依赖倒置原则和单一职责原则,使得模块之间的界限模糊,增加了系统的耦合度。

     3.4. 运行时异常:在某些情况下,循环依赖可能导致运行时异常,尤其是在使用构造器注入时,Spring无法解决构造器级别的循环依赖。

二、解决循环依赖的方案

4、Spring循环依赖的解决方案:

在提出解决方案之前,可以先了解Spring bean的生命周期,Spring Bean的生命周期大体上分为三个阶段:
   ① Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的, 是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的
Bean通过反射进行实例化;
  ② Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware 接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段;
  ③ Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池
singletonObjects中去了,即完成了Spring Bean的整个生命周期。

三、三级缓存池概述

Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map                       

/** 一级缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 三级缓存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** 二级缓存 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

4、三个Map大致区别:

      ① Map类型不同:一级、二级缓存为ConcurrentHashMap,三级缓存池为HashMap

      ② 容量不同:一级缓存为256,二级为16,三级缓存也为16

      ③ 范型不同:一级以及二级为Object类型,三级缓存池为ObjectFactory类型

5、三级缓存包括:

      概述:

  ① 一级缓存池(singletonObjects):存储已经完全实例化和初始化的Bean实例,可通过ApplicationContext接口的getBean() 方法,直接获取实例化对象。
  ② 二级缓存池(earlySingletonObjects):存储尚未完全初始化但已经足够用于属性注入的Bean实例,这些实例被称为“半成品”。
  ③  三级缓存池(singletonFactories):存储创建Bean实例的工厂对象,和二级缓存池相似,都是用来存放暂时不完整的Bean对象的容器。但与二级缓存池有不同,此缓存池存放的对象没有被其他类引用,只存放不完整的Bean对象。
6、关于循环依赖简单案例

假设有两个Bean A 和   B,它们之间存在循环依赖:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

         

当Spring容器创建Bean A时,它会发现需要注入尚未完全初始化的Bean B。此时,容器会使用三级缓存机制:

  1. 实例化Bean A:在实例化后,容器会立即将Bean A的半成品(尚未填充属性)放入二级缓存。
  2. 创建Bean B:在创建Bean B的过程中,容器会发现需要注入Bean A。由于Bean A的半成品已经在二级缓存中,容器会继续实例化Bean B
  3. 循环依赖的解决:在Bean B的属性填充阶段,容器会从二级缓存中取出Bean A的半成品,并将其放入一级缓存。这样,Bean B就可以继续其初始化过程,而Bean A也可以在稍后完成其初始化。

通过这种方式,Spring容器能够在不违反Bean的单例作用域和依赖注入原则的前提下,处理复杂的循环依赖情况. 

                   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一只鱼……

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

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

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

打赏作者

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

抵扣说明:

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

余额充值