spring循环依赖深度源码解析

ops/2024/12/18 10:35:45/

spring_0">spring循环依赖深度源码解析

一,什么是循环依赖问题

简单来说循环依赖就是在spring容器中的两个Bean互相调用对方

在这里我们创建了两个对象A,B,在A中调用B,在B中调用A,这样就会产生循环依赖问题

public class A {private B b;public B getB() {return b;}public void setB(B b) {this.b = b;}
}
public class B {private A a;public A getA() {return a;}public void setA(A a) {this.a = a;}
}
<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 id="a" class="com.jixu.DTO.A"><property name="b" ref="b"></property></bean><bean id="b" class="com.jixu.DTO.B"><property name="a" ref="a"></property></bean>
</beans>

在这里插入图片描述

二,什么是三级缓存

三级缓存是咋spring的DefaultSingletenBeanRegister这个类当中

// 一级缓存
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);

在这里最值得注意的是三级缓存,我们看一下它的类型Map<String, ObjectFactory<?>>,它的key的类型为 ObjectFactory<?>,我们进入源码来看一下这是个什么东西

// 函数式接口 --》 只有当调用getObject时候才会真正执行代码
@FunctionalInterface
public interface ObjectFactory<T> {/*** Return an instance (possibly shared or independent)* of the object managed by this factory.* @return the resulting instance* @throws BeansException in case of creation errors*/T getObject() throws BeansException;}

它使用了一个@FunctionalInterface也就是函数式接口,这样的话在三级缓存当中存放的就是Lambda表达式,而且只有当调用了其getObject方法的时候才会实际调用Lambda当中的代码

为什么spring要这样设计,提前暴露bean?那我们来想一个问题,如果对象是代理对象该怎么办,我们在创建的时候并不能知道该对象是代理对象还是普通对象,只有在实际调用的时候才能知道具体类型。那么函数式接口就可以完美解决这个问题,在实际调用的时候再使用getObject方法根据其类型实际创建对象。

三,全流程debug

在这里我们从AbstractApplicationContext这个类当中的refresh方法开始打断点

在这里插入图片描述

此时在我们的beanFactory当中也可以找到对应的三级缓存

在这里插入图片描述

按F8一直到preInstantiateSingletons这个方法中该方法是用来创建Bean的

在其中的beanNames当中就保存着我们所有注册的bean对象的beanDefinition信息,在这里就可以看到A,B对象,之后会循环A,B创建对象

在这里插入图片描述

在这里会进行一系列判断,之后会调用getBean方法,在getBean方法当中会调用doGetBean方法(通常以do开头的是实际执行的方法)

public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}

在doGetBean当中会去判断对象在三级缓存当中是否存在

// 判断在三级缓存当中是否存在该对象
Object sharedInstance = getSingleton(beanName);
public Object getSingleton(String beanName) {return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}

如果不存在就会去调用其createBean方法创建Bean,快进到方法位置

// 创建Bean实例
if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

在这里插入图片描述

在这里会进行一个判断,判断是否为单列对象,通过getSingleton方法传入bean名称与lambda表达式回调

createBean方法。在其createbean方法中会调用doCreateBean方法执行具体的实例化及属性赋值流程

// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
// 创建通过反射创建对象
if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 获取到对象
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;
}

在这里插入图片描述

在doCreateBean当中会提前暴露对象,通过反射创建对象实例,其属性赋值为空。将将key=bean名称,value=lambda表达式加入三级缓存当中

这个方法的主要作用是在bean的生命周期中提供一个早期访问点,允许某些特定的处理或操作在bean完全初始化之前进行。这在需要对bean进行特殊处理,或者在bean初始化过程中需要引用同一bean的早期状态时非常有用。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;
}

调用其populateBean方法为A的属性进行赋值

在这里插入图片描述

在这里如果属性值为另一个Bean的引用时其值的类型为runTimeReference

循环所有属性进行赋值判断其类型是否为runTimeReference如果是则调用其getBean方法获取对象

在这里插入图片描述

在这里插入图片描述

在doGetBean当中就会尝试获取B的Bean,如果Bean不存在则创建B和上述流程一样

在这里插入图片描述

最后将B的lambda加入到三级缓存当中,调用populateBean方法赋值属性

在这里插入图片描述

此时B中需要注入A

在这里插入图片描述

此时在三级缓存当中就可以查询到Bean信息,之后会调用其getObject方法回调函数

// 从单例池当中获取对象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果单例池当中不存在,且对象正在创建过程中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 从二级缓存当中获取对象singletonObject = this.earlySingletonObjects.get(beanName);// 如果二级缓存中不存在if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton lock// 再次检查singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {// 从三级缓存当中获取到对应的lambda表达式ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 调用其getObject方法singletonObject = singletonFactory.getObject();// 加入二级缓存this.earlySingletonObjects.put(beanName, singletonObject);// 从三级缓存当中删除this.singletonFactories.remove(beanName);}}}}

在这里将bean赋值给了exposedObject这也就是所谓的提前暴露对象, 通过该方法可以判断出是否有beanPostProcesser的增强,也就是是否为代理对象对其进行代理,如果不是则返回普通对象

在这里插入图片描述

此时就生成了A的半成品对象,并加入了二级缓存,之后对属性值赋值

在这里插入图片描述

执行完毕后将B对象加入一级缓存,此时B的完整Bean对象已经生成

protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}

之后将B注入给A,将A对象加入一级缓存移除其二级缓存的数据


http://www.ppmy.cn/ops/142885.html

相关文章

国产Linux系统如何部署ftp文件共享服务器

在Linux系统上部署FTP&#xff08;文件传输协议&#xff09;文件共享服务器通常涉及安装和配置FTP服务器软件。最常用的FTP服务器软件之一是vsftpd&#xff08;Very Secure FTP Daemon&#xff09;。以下是如何在Linux上部署FTP文件共享服务器的步骤&#xff1a; 一、安装vsft…

[C++]运算符重载

一、 什么是运算符重载&#xff1f; 运算符重载是 C 中的一种功能&#xff0c;它允许用户定义的类或数据类型重新定义或扩展运算符的行为&#xff0c;使运算符能够作用于用户定义的对象。 二、 通俗解释 在 C 中&#xff0c;运算符&#xff08;如 , -, *, 等&#xff09;默认…

电气CAD制图软件概述及主要电气CAD软件介绍

一、电气CAD制图软件概述 电气CAD制图软件&#xff0c;即电气计算机辅助设计软件&#xff0c;是一种用于电气系统设计的专业软件。这类软件能够通过计算机帮助电气工程师完成从简单的电路设计到复杂的电气系统设计等各种任务。常用的电气CAD制图软件主要有AutoCAD, EPLAN,SEE E…

Java Math、System、Runtime

1. Math (1) 代表数学&#xff0c;是一个工具类&#xff0c;里面提供的都是对数据操作的一些静态方法。 (2) Math 类常见的方法&#xff1a; 方法说明public static int abs(int a)获取参数的绝对值(其他基本类型方法相同)public static double ceil(double a)向上取整public …

️Java如何根据前端返回的字段名进行动态数据查询?——深入探究与实战演练

全文目录&#xff1a; 开篇语前言&#x1f4dc;目录&#x1f304; 前言&#x1f9d0; 场景分析&#xff1a;为什么要根据字段名查询&#xff1f;&#x1f4a1; 设计思路&#xff1a;如何实现动态查询&#xff1f;1. 动态构造查询条件2. 使用映射数据结构存储字段条件3. 考虑使用…

6.3.1 MR实战:计算总分与平均分

在本次实战中&#xff0c;我们的目标是利用Apache Hadoop的MapReduce框架来处理和分析学生成绩数据。具体来说&#xff0c;我们将计算一个包含五名学生五门科目成绩的数据集的总分和平均分。这个过程包括在云主机上准备数据&#xff0c;将成绩数据存储为文本文件&#xff0c;并…

三维测量与建模笔记 - 7.2 点云滤波

逐点计算法向量&#xff0c;需要对每一个点拟合出它的切平面&#xff0c;一般使用邻域点信息来查找切平面。 选取要计算的点和它周围一定范围内的点可以拟合出一个平面&#xff0c;最基本的方法是通过最小二乘法取对这些点到平面的距离进行优化&#xff08;计算量很大&#xff…

Java集合类 HashMap 深度解读(含源码解析)

目录 HashMap基本概念 什么是HashMap HashMap的特点 HashMap类的继承和实现关系 深入了解HashMap前需要知道 hashCode()和equals()方法的关系 重写hashCode()方法的基本规则 HashMap的底层数据结构 JDK 1.8后采用数组 链表 红黑树 负载因子与扩容机制 为什么默认负…