手写spring IOC底层源码来模拟spring如何利用多级缓存解决循环依赖的问题
在文章开始之前,先来看一张spring IOC加载过程的脑图吧

Spring IOC的加载过程
首先,当我们去new了一个applicationContext,它底层呢就会把我们配置的bean进行扫描,然后创建成一个一个的beanDefinition放在我们的beanDefinitionMap中,此时就有了一切创造bean的原料信息,然后就会去循环beanDefinition,去调用beanfactory.getBean方法,先尝试在一级缓存中获取,获取不到呢就会进行创建,先进行实例化,然后进行依赖注入,最后初始化,放入到一级缓存中.
手写源码
package cn.edu.hunau;import cn.edu.hunau.service.AService;
import cn.edu.hunau.service.BService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @Author SuJ* @Date 2024 04 12 15 13.* 手写spring IOC底层源码来模拟spring如何利用多级缓存解决循环依赖的问题。**/
public class SuJApplicationContext {private Map<String, BeanDefinition> beanDefinitionMap = new LinkedHashMap<>();// 一级缓存 单例池private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();public SuJApplicationContext() throws Exception{// 加载ioc容器 创建所有的beanrefersh();finishBeanFactoryInitialization();}//一个个的创建beanprivate void finishBeanFactoryInitialization() {//循环所有的beanDefinitionbeanDefinitionMap.keySet().forEach(beanName -> {try {getBean(beanName);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}});}private Object getBean(String beanName) throws InstantiationException, IllegalAccessException {// 1.尝试在一级缓存中获取Object bean = getSingleton(beanName);//如果存在 直接放回if(bean != null){return bean;}// 2.创建 ---> 实例化RootBeanDefinition beanDefinition = (RootBeanDefinition)beanDefinitionMap.get(beanName);Class<?> beanClass = beanDefinition.getBeanClass();bean = beanClass.newInstance();//3. 依赖注入for (Field declaredField : beanClass.getDeclaredFields()){//当前属性有注解if(declaredField.getAnnotation(Autowired.class) != null){String name = declaredField.getName();Object dependBean = getBean(name);declaredField.setAccessible(true);declaredField.set(bean, dependBean);}}//4.初始化
//if(bean instanceof InitializingBean){
// ((InitializingBean)bean).afterPropertiesSet();
//}//5.放入一级缓存singletonObjects.put(beanName, bean);return bean;}
//获取单例池中的beanprivate Object getSingleton(String beanName) {if(singletonObjects.containsKey(beanName)){return singletonObjects.get(beanName);}return null;}//ioc容器加载public void refersh() throws Exception{//1.解析配置 支持BeanDefinitionloadBeanDefinitions();}/**** 根据配置信息创建BeanDefinition 底层是通过解析配置类注册beandefiniton*/private void loadBeanDefinitions(){// 创建A BeanDefinitionRootBeanDefinition aBeanDefinition = new RootBeanDefinition(AService.class);//创建B BeanDefinitionRootBeanDefinition bBeanDefinition = new RootBeanDefinition(BService.class);beanDefinitionMap.put("aService",aBeanDefinition);beanDefinitionMap.put("bService",bBeanDefinition);}
}
当我们手写完IOC容器的创建过程,会发现其实在一级缓存就可以解决循环依赖的问题,只需要增加一行代码。

我们可以发现程序正常执行

那为什么spring的设计人员不采取这种方式,而是要通过三级缓存来解决循环依赖的问题呢?
这是因为只通过一级缓存来解决循环依赖问题会造成线程安全问题,例如线程1先实例化A,直接放到一级缓存,这时线程2从一级缓存中获取到了实例,调用B实例的方法,由于没有进行依赖注入,我们的B实例为null,会造成空指针异常。
为了解决这个问题,我们引入了二级缓存,专门用于存储不完整的bean,使用二级缓存获取到的bean作为出口,并且将临界资源锁住(这里借用了单例模式的思想),果然解决了线程安全的问题。

package cn.edu.hunau;import cn.edu.hunau.service.AService;
import cn.edu.hunau.service.BService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @Author SuJ* @Date 2024 04 12 15 13.* 手写spring IOC底层源码来模拟spring如何利用多级缓存解决循环依赖的问题。**/
public class SuJApplicationContext {private Map<String, BeanDefinition> beanDefinitionMap = new LinkedHashMap<>();// 一级缓存 单例池private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();// 二级缓存 ----> 并发获取不完整bean------dclprivate final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();public SuJApplicationContext() throws Exception {// 加载ioc容器 创建所有的beanrefersh();finishBeanFactoryInitialization();}//一个个的创建beanprivate void finishBeanFactoryInitialization() {//循环所有的beanDefinitionbeanDefinitionMap.keySet().forEach(beanName -> {try {getBean(beanName);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}});}private Object getBean(String beanName) throws InstantiationException, IllegalAccessException {// 1.尝试在一级缓存中获取Object bean = getSingleton(beanName);//如果存在 直接放回if (bean != null) {return bean;}synchronized (singletonObjects) {bean = getSingleton(beanName);//如果存在 直接返回if (bean != null) {return bean;}// 2.创建 ---> 实例化RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);Class<?> beanClass = beanDefinition.getBeanClass();bean = beanClass.newInstance();earlySingletonObjects.put(beanName, bean);//3. 依赖注入for (Field declaredField : beanClass.getDeclaredFields()) {//当前属性有注解if (declaredField.getAnnotation(Autowired.class) != null) {String name = declaredField.getName();Object dependBean = getBean(name);declaredField.setAccessible(true);declaredField.set(bean, dependBean);}}//4.初始化
//if(bean instanceof InitializingBean){
// ((InitializingBean)bean).afterPropertiesSet();
//}//5.放入一级缓存singletonObjects.put(beanName, bean);earlySingletonObjects.remove(beanName); //二级缓存是临时的需要清楚return bean;}}//获取单例池中的beanprivate Object getSingleton(String beanName) {if (singletonObjects.containsKey(beanName)) {return singletonObjects.get(beanName);}//出口synchronized (singletonObjects) {if (earlySingletonObjects.containsKey(beanName)) {return earlySingletonObjects.get(beanName);}}return null;}//ioc容器加载public void refersh() throws Exception {//1.解析配置 支持BeanDefinitionloadBeanDefinitions();}/*** 根据配置信息创建BeanDefinition 底层是通过解析配置类注册beandefiniton*/private void loadBeanDefinitions() {// 创建A BeanDefinitionRootBeanDefinition aBeanDefinition = new RootBeanDefinition(AService.class);//创建B BeanDefinitionRootBeanDefinition bBeanDefinition = new RootBeanDefinition(BService.class);beanDefinitionMap.put("aService", aBeanDefinition);beanDefinitionMap.put("bService", bBeanDefinition);}
}
那三级缓存用来干什么的?
三级缓存主要是处理我们涉及到需要代理的Bean的情况的。一般来说,动态代理需要Bean的初始化过程中进行创建,但是在循环依赖的这种特殊情况下,程序根本无法走到初始化这一步,所以我们需要在实例化后就进行Bean的增强。假如说我们只使用二级缓存(如下图这样写的话),对于需要进行增强的Bean会造成两个问题
1.没有遵循规范(初始化再增强
2.循环依赖多次会创建多次(A和B循环依赖,A和C循环依赖

为了解决这些问题,spring的底层引入了三级缓存(存储一个Bean工厂对象,对于需要做增强的Bean返回代理类,不需要的返回原始类)
package cn.edu.hunau;import cn.edu.hunau.service.impl.AService;
import cn.edu.hunau.service.impl.BService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @Author SuJ* @Date 2024 04 12 15 13.* 手写spring IOC底层源码来模拟spring如何利用多级缓存解决循环依赖的问题。**/
public class SuJApplicationContext {private Map<String, BeanDefinition> beanDefinitionMap = new LinkedHashMap<>();// 一级缓存 单例池private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();// 二级缓存 ----> 并发获取不完整bean------dclprivate final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();//三级缓存private final Map<String, ObjectFactory> factoriesEarlySingletonObjects = new ConcurrentHashMap<>();public SuJApplicationContext() throws Exception {// 加载ioc容器 创建所有的beanrefersh();finishBeanFactoryInitialization();}//一个个的创建beanprivate void finishBeanFactoryInitialization() {//循环所有的beanDefinitionbeanDefinitionMap.keySet().forEach(beanName -> {try {getBean(beanName);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}});}public Object getBean(String beanName) throws InstantiationException, IllegalAccessException {// 1.尝试在一级缓存中获取Object bean = getSingleton(beanName);//如果存在 直接放回if (bean != null) {return bean;}synchronized (singletonObjects) {bean = getSingleton(beanName);//如果存在 直接返回if (bean != null) {return bean;}// 2.创建 ---> 实例化RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);Class<?> beanClass = beanDefinition.getBeanClass();Object beanNew = beanClass.newInstance();//1.没有遵循规范 2.循环依赖多次会创建多次Object beanAop = new JdkProxyBeanPostProcessor().getEarlyBeanReference(bean, beanName);factoriesEarlySingletonObjects.put(beanName, ()->{return new JdkProxyBeanPostProcessor().getEarlyBeanReference(beanNew,beanName );});// 首先将早期引用放入二级缓存
// earlySingletonObjects.put(beanName, beanNew);//3. 依赖注入for (Field declaredField : beanClass.getDeclaredFields()) {//当前属性有注解if (declaredField.getAnnotation(Autowired.class) != null) {String name = declaredField.getName();Object dependBean = getBean(name);declaredField.setAccessible(true);declaredField.set(beanNew, dependBean);}}//4.初始化
//if(bean instanceof InitializingBean){
// ((InitializingBean)bean).afterPropertiesSet();
//}//5.放入一级缓存singletonObjects.put(beanName, beanNew);earlySingletonObjects.remove(beanName); //二级缓存是临时的需要清除factoriesEarlySingletonObjects.remove(beanName); //三级缓存是临时的需要清除return beanNew;}}private Object getSingleton(String beanName) {if (singletonObjects.containsKey(beanName)) {return singletonObjects.get(beanName);}//出口 -- 当前是循环依赖synchronized (singletonObjects) {if (earlySingletonObjects.containsKey(beanName)) {return earlySingletonObjects.get(beanName);}if (factoriesEarlySingletonObjects.containsKey(beanName)) {ObjectFactory objectFactory = factoriesEarlySingletonObjects.get(beanName);// aopObject object = objectFactory.getObject();earlySingletonObjects.put(beanName, object); //解决循环依赖多次会创建多次的问题return object;}}return null;}//ioc容器加载public void refersh() throws Exception {//1.解析配置 支持BeanDefinitionloadBeanDefinitions();}/*** 根据配置信息创建BeanDefinition 底层是通过解析配置类注册beandefiniton*/private void loadBeanDefinitions() {// 创建A BeanDefinitionRootBeanDefinition aBeanDefinition = new RootBeanDefinition(AService.class);//创建B BeanDefinitionRootBeanDefinition bBeanDefinition = new RootBeanDefinition(BService.class);beanDefinitionMap.put("aService", aBeanDefinition);beanDefinitionMap.put("bService", bBeanDefinition);}
}
其实三级缓存的思想就是:在实例化后不是直接动态代理,而是其函数式接口放入三级缓存中,出现循环依赖时在进行调用创建代理的函数。



以上是我个人的见解,请大家多指教
相关文章:
手写spring IOC底层源码来模拟spring如何利用多级缓存解决循环依赖的问题
在文章开始之前,先来看一张spring IOC加载过程的脑图吧 Spring IOC的加载过程 首先,当我们去new了一个applicationContext,它底层呢就会把我们配置的bean进行扫描,然后创建成一个一个的beanDefinition放在我们的beanDefinitionMap中,此时就有了一切创造bean的原料信…...
C++11 Thead线程和线程池
参考资料: 2、5.lock_guard 与 std::unique_lock-陈子青的编程学习课堂 (seestudy.cn) 3、C11 多线程编程-小白零基础到手撕线程池_哔哩哔哩_bilibili 一、 C11 Thead线程库的基本使用 # include <thread> std::thread t(function_name, args...); // 线…...
Windows版Apache 2.4.59解压直用(免安装-绿色-项目打包直接使用)
windows下Apache分类 Apache分为 安装版和解压版 安装版: 安装方便,下一步------下一步就OK了,但重装系统更换环境又要重新来一遍,会特别麻烦 解压版(推荐): 这种方式(项目打包特别方便&#x…...
刀具表面上的微结构
刀具表面微结构通常指在刀具表面对特定功能设计的微观纹理,这些纹理可以是沟槽、凹坑、凸起或任何其他形式的微观图案。这些微结构的设计和应用是为了改善刀具的切削性能,减少切削力和切削温度,提高切削效率和精度,同时降低切削液…...
css3实现微信扫码登陆动画
在做微信扫码登陆时,出现一个背景光图上下扫码动画,用css3图片实现。 实现原理: 1.准备一个渐变的背景.png图 2.css动画帧实现动画 看效果: css代码: #wx-scan{position: absolute;top:0px;left: 50%;z-index: 3;ma…...
vue3 导入excel数据
所需包 "xlsx": "^0.18.5"页面导入包 import * as XLSX from xlsx; import {genFileId, UploadProps, UploadRawFile,ElTable } from element-plus;页面 <el-upload accept".xlsx" :on-change"changeExcel" :on-exceed"ha…...
C# linq 根据多字段动态Group by
实现类: public static class LinqHepler {/// <summary>/// 根据单个字段动态Group/// </summary>/// <typeparam name"T"></typeparam>/// <param name"source"></param>/// <param name"prop…...
C语言学习/复习22----阶段测评编程题
一、阶段测评练习 题1: 题2:...
LeetCode-1766. 互质树【树 深度优先搜索 广度优先搜索 数组 数学 数论】
LeetCode-1766. 互质树【树 深度优先搜索 广度优先搜索 数组 数学 数论】 题目描述:解题思路一:DFS 中记录节点值的深度和编号,回溯写法。关键点是1 < nums[i] < 50解题思路二:0解题思路三:0 题目描述࿱…...
“数据安全服务能力”评定资格认证!不容错过
数据安全服务能力评定是指对数据安全服务提供商从事数据安全服务综合能力的评定,包括技术能力、服务能力、质量保证能力、人员构成与素质、经营业绩、资产状况等要素。 一、能力评定类型与等级 数据安全服务能力分为二个类型:数据安全评估、数据安全建…...
【MATLAB 分类算法教程】_3麻雀搜索算法优化支持向量机SVM分类 - 教程和对应MATLAB代码
分类代码案例3:麻雀搜索算法优化支持向量机SVM分类 - MATLAB完全代码教程 1. 初始化代码2.读取数据代码3.数据预处理代码4.利用麻雀搜索算法SSA求解最佳的SVM参数c和g代码5.根据最佳的参数进行SVM模型训练代码6.SVM模型预测代码7.准确率分析以及分类结果对比作图代码本文以红酒…...
利用机器学习库做动态定价策略的例子
动态定价是一个复杂的问题,涉及到市场需求、库存、竞争对手行为、季节性因素等多个变量。在实际应用中,动态定价通常需要复杂的模型和大量的数据分析。我选择使用Python(Golearn库)进行机器学习模型的训练和部署,而将G…...
Tcpdump -r 解析pcap文件
当我们使用命令抓包后,想在命令行直接读取筛选怎么办?-r参数就支持了这个 当你使用 tcpdump 的 -r 选项读取一个之前捕获的数据包文件,并想要筛选指定 IP 地址和端口的包时,你可以在命令中直接加入过滤表达式。这些过滤表达式可以…...
[dvwa] sql injection(Blind)
blind 0x01 low 1’ and length(version()) 6 # syntax: substr(string , from<start from 1>, cut length) 1’ and substr(version(),1,1) ‘5’ # 1’ and substr(version(),2,1) ‘.’ # 1’ and substr(version(),3,1) ‘7’ # 1’ and substr(version(),4,…...
linux 挂载云盘 NT只能挂载2T,使用parted挂载超过2T云盘
一、删除原来挂载好的云盘和分区 1、查看挂载号的云盘 fdisk -l 发现我们有5千多G但是只挂载了2T,心里非常的慌张!十分的不爽! 好,我们把它干掉,重新分区! 2、解除挂载 umount /homeE 没保存跳转到&…...
用Skimage学习数字图像处理(021):图像特征提取之线检测(下)
本节是特征提取之线检测的下篇,讨论基于Hough变换的线检测方法。首先简要介绍Hough变换的基本原理,然后重点介绍Skimage中含有的基于Hough变换的直线和圆形检测到实现。 目录 10.4 Hough变换 10.4.1 原理 10.4.2 实现 10.4 Hough变换 Hough变换&…...
ArduPilot飞控之Gazebo + SITL + MP的Jetson Orin环境搭建
ArduPilot飞控之Gazebo SITL MP的Jetson Orin环境搭建 1. 源由2. Linux环境整理3. 安装Gazebo环境3.1 安装Gazebo3.2 安装插件3.3 配置插件3.4 测试Gazebo 4. 安装Arudpilot-SITL环境4.1 克隆工程4.2 编译准备4.3 环境配置4.4 配置编译4.5 测试运行 5. 测试运行6. 参考资料 1…...
前端错误监控的方法有哪些
前端错误监控是指通过各种手段收集、分析和处理前端应用运行中发生的错误 常用的前端错误监控的方法有 使用 try catch 方法 捕获特定代码块中的错误多用于处理特定函数或代码段可能抛出的异常,尤其是异步代码网络请求错误监控 promise.catchtry catch全局错误处理…...
✌粤嵌—2024/3/11—跳跃游戏
代码实现: 方法一:递归记忆化 int path; int used[10000];bool dfs(int *nums, int numsSize) {if (path numsSize - 1) {return true;}for (int i 1; i < nums[path]; i) {if (used[path i]) {continue;}path i;used[path] 1;if (dfs(nums, num…...
Docker入门实战教程
文章目录 Docker引擎的安装Docker比vm虚拟机快 Docker常用命令帮助启动类命令镜像命令docker imagesdocker searchdocker pulldocker system dfdocker rmi 容器命令redis前台交互式启动redis后台守护式启动Nginx容器运行ubuntu交互式运行tomcat交互式运行对外暴露访问端口 Dock…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 。它的特点包括&a…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
