Spring循环依赖解决姿势&源码解读

前提

  • 写过代码

  • 了解Spring

    哈哈,只要符合上面2个,那么我们就边走边看,源码解读带你了解Spring对循环依赖的姿势

概念说明

  • 什么是循环依赖,同一个项目有如下结构的类
    在这里插入图片描述

铺垫

首先我们来对Spring的主线过下,spring容器启动其实就主要2点实例化初始化

实例化

  • 对bean进行创建,但是还未对成员变量赋值

初始化

  • 对创建的bean里的成员变量赋值,和相关的初始化方法执行

进入Spring循环依赖的姿势

前提概要
认识循环依赖的姿势 :三级缓存

DefaultSingletonBeanRegistry.java

一级缓存 存放实例化和初始化完成的成品对象
/** Cache of singleton objects: bean name to bean instance.*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

三级缓存 key,value;  value就是ObjectFactory 一个函数式,循环依赖中主要是存放lambda表达式
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

二级缓存 循环依赖中存放半成品对象
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

工程搭建
工具:idea
Spring版本:5.1.6.RELEASE

  • 打开idea工程,准备以下简单Spring工程,只需要依赖spring-context在这里插入图片描述
  • A类
 package com.spring.cycle;
 public class A {
    private B b;

    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
}
  • B类
package com.spring.cycle;
public class B {
    private String name;
    private A a;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
}
  • 配置文件cycle.xml
<?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 name="a" class="com.spring.cycle.A">
        <property name="b" ref="b"></property>
    </bean>
    <bean name="b" class="com.spring.cycle.B">
        <!-- 通过属性的set方法给对象赋值 -->
        <property name="a" ref="a"></property>
        <property name="name" value="王老五"></property>
    </bean>
</beans>
  • 测试类
package com.spring.cycle;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestCycle {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("cycle.xml");
        A a = (A) applicationContext.getBean("a");
        B b = (B) applicationContext.getBean("b");
        System.out.println(a);
        System.out.println(a.getB());
        System.out.println("--------------------");
        System.out.println(b);
        System.out.println(b.getA());
        System.out.println(b.getName());
    }
}

撸源码
核心几个方法
getBean->doGetBean->createBean->doCreateBean->createBeanInstance->populateBean
Spring核心主流程可以看下前面的文章
Spring启动主线流程源码分析
Spring容器启动中XML解析

  • 针对上面简单循环依赖debug走起
    在这里插入图片描述
    1 首先创建A对象
    在这里插入图片描述
    2 先从缓存获取,第一次创建A肯定不存在,返回空
    在这里插入图片描述
    3 开始创建A对象
    在这里插入图片描述
    4 进入getSingleton方法
    在这里插入图片描述
    5 执行getSingleton方法中的singletonFactory.getObject(),就走到函数式接口,开始执行createBean方法
    在这里插入图片描述
    6 开始创建和初始化成员变量
    在这里插入图片描述
    7 createBeanInstance创建对象
    在这里插入图片描述
    在这里插入图片描述
    populateBean给A对象初始化成员变量
    在这里插入图片描述
    8 找出A对象的成员变量列表,发现存在一个成员变量b
    在这里插入图片描述
    9 遍历A对象的成员变量列表,一个一个设置成员变量
    在这里插入图片描述
    10 给A对象设置成员变量b时,又从bean工厂获取b对象
    在这里插入图片描述
    11 此时给A对象的b成员变量赋值,走到创建B对象流程
    在这里插入图片描述
    12 创建B对象还是先从缓存获取对象
    在这里插入图片描述
    13 创建B对象又走到getSingleton方法,先设置B对象创建标识,其实就是Set数据结构设置一个bean的名字。this.singletonsCurrentlyInCreation.add(beanName)
    在这里插入图片描述
    14 创建B对象开始createBeanInstance,创建后可以看到B对象里面的成员变量name和a都是空的
    在这里插入图片描述
    15 创建B对象刚刚完成后,先放入三级缓存
    在这里插入图片描述
    16 同时此时可以看到三级缓存已经存在a和b的创建对象后的一个lambda表达式
    在这里插入图片描述
    同时把对应的二级缓存清理该bean的信息
    在这里插入图片描述
    17 B对象创建完成且放入到三级缓存后,然后开始给B对象的name和a成员变量赋值
    在这里插入图片描述
    18 先获取到B对象的成员变量列表(a和name)
    在这里插入图片描述
    先设置成员变量a
    在这里插入图片描述
    因为成员变量是RuntimeBeanReference一个引用,所以又从bean工厂获取a对象
    在这里插入图片描述
    还是先从缓存获取a对象
    在这里插入图片描述
    此时三级缓存中存在a对象应用,所以执行a对象引用的lambda表达式获取a的一个半成品对象(a对象的成员变量b还是空),放入二级缓存,清理在三级缓存的a对象信息
    在这里插入图片描述
    A对象获取到后赋值给B对象的成员变量a
    在这里插入图片描述
    此时是给B对象赋值成员变量name,因为是string类型,直接设置即可
    在这里插入图片描述
    19 此时完成B对象创建和B对象的成员变量的赋值
    在这里插入图片描述
    后面的initializeBean方法功能,主要判断是否实现了某些接口,执行相应的方法。实现了InitializingBean接口的类,就在这个函数里执行afterPropertiesSet方法的,所以这个时候类已经实例化完成,但是成员变量不一定赋值,也就是可能存在一个半成品对象的情况
    20 B对象已经创建和实例化完成,清理类创建标识this.singletonsCurrentlyInCreation.remove(beanName),然后加入一级缓存,清理对应的二三级缓存信息
    在这里插入图片描述

21 回到A对象赋值成员变量的现场,刚刚饶了一大圈才创建的B对象,目的是为了给A对象赋值成员变量b
在这里插入图片描述
此时看到的A对象已经是一个成品对象,里面的b也是一个成品对象
在这里插入图片描述
接下来也是执行A对象的initializeBean方法,完事后就执行创建对象后的收尾工作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时的A对象才刚刚结束,继续下一个bean的创建
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
至此A,B类的实例化和初始化已经都完成了。循环依赖过程都通过断点形式走了一遍。

总结

整个按理Spring创建bean和初始化过程如下
在这里插入图片描述
问题:如果只有一级缓存能不能解决循环依赖?
不行
从上面调试过程中,发现一个对象存在成品和半成品的状态,如果都放入一个缓存里面,那么其他对象获取到一个半成品对象使用,就会报错,因此需要区分出成品对象和半成品对象的存放地,那么就至少需要二级缓存。一级缓存存放成品对象,二级缓存存放半成品对象。
问题:循环依赖是否只要二级缓存就可以解决?
如果整个项目中没有AOP功能,二级缓存可以解决循环依赖。
修改代码验证. 只从1,2级缓存获取
在这里插入图片描述
实例化后直接加入二级缓存
在这里插入图片描述

1 修改上面二处代码,我们直接运行我们的测试类,发现完全可以支撑循环依赖
在这里插入图片描述
问题项目中存在AOP功能,只有二级缓存会存在什么问题
我们代码加入AOP功能,执行运行原始代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后我们修改回原始代码,对于AOP完全可以
在这里插入图片描述
问题:为什么构造函数不支持循环依赖
因为构造函数在对象实例化过程中发生的,不会把实例化的信息放入缓存,所以会导致死循环。而通过setter方式解决循环依赖的核心是因为把对象分成实例化和初始化2步,并且实例化完成后会把实例化(未初始化)的对象放入3级缓存中,后面注入时就可以从缓存获取,从而解决循环依赖


关于AOP的详细介绍,我们在后面再次详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值