【图文详解】Spring是如何解决循环依赖的?

news2024/11/15 22:51:28

Spring是如何解决循环依赖的呢?

很多小伙伴在面试时都被问到过这个问题,刷到过这个题的同学马上就能回答出来:“利用三级缓存”。面试官接着追问:“哪三级缓存呢?用两级行不行呢?” 这时候如果没有深入研究过Spring源码的话,估计就要根据自己的理解开始胡诌了。为了帮大家彻底理清这个问题,吊打面试官,我特意出这篇文章来帮你梳理一下Spring的解决思路。

解决循环依赖的前提是创建的Bean是都单例的,本章的所有Bean默认都采用单例模式。

我假定屏幕前的你已经熟练使用Spring并且知道IOC的原理了。

Spring 的 BeanFactory 在创建 Bean 时会经历三个重要步骤

  1. 实例化bean (createBeanInstance)
  2. 给bean的属性赋值 (populateBean)
  3. 初始化bean (initializeBean)

每个Bean在被创建时都需要按部就班走完这三步后,才能诞生一个可用的Bean。

先看一个没有循环依赖的例子

假如一个Bean(AService)持有另一个Bean(BService) 的引用,两个Bean是怎么个创建步骤呢?

AService与BService的类定义如下

@Service
public class AService {

    @Autowired
    BService bService;

    public void seeA() {
        bService.eat();
    }

}
@Service
public class BService {

    public void eat() {
        System.out.println("B======eat");
    }

}

AService持有一个BService的引用,如果在创建AService这个Bean时,BService还没创建的话,那么BService也会被创建。流程如下图

可以看到这两个Bean的创建流程很明了。

Bean创建完成后会放到一个单例池中,这个单例池就是三级缓存中的“一级缓存”。

Spring用一个Map来充当这个单例池,key是beanName,value为Bean对象。

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

构建一个循环依赖(不考虑AOP代理逻辑)

假如BService中又引用了AService,两个Bean各自依赖对方,形成了循环依赖,如果还沿用上面的流程来创建Bean的话,就会陷入到一个无止境的循环中。

现在AService和BService的代码如下

@Service
public class AService {

    @Autowired
    BService bService;

    public void seeA() {
        bService.eat();
    }

    public void eat() {
        System.out.println("A======eat");
    }

}
@Service
public class BService {

    @Autowired
    AService aService;

    public void seeB() {
        aService.eat();
    }


    public void eat() {
        System.out.println("B======eat");
    }

}

怎么打破这个循环呢?

Spring是这样想的:创建一个Bean需要经过三个步骤,在第一步实例化Bean这个操作完成后,这个Bean对象在内存中就已经存在了,就能拿到该对象的引用了,但是这个对象还是个半成品,之后的第二、三步都是在丰富这个对象,第三步走完后这个对象才是个成品对象。所以第一步走完后,就可以找个地方将该Bean的引用存放起来,因为还是个半成品,所以不能放到“一级缓存”中, Spring就为它创建了一个新地方,这个地方就是“二级缓存”,Spring用一个Map来表示二级缓存,key是beanName,value为Bean半成品对象。

private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

拿上面的例子来说,AService在被实例化后,Spring就将AService的引用放到二级缓存中,然后第二步填充属性时触发了BService的Bean创建流程,BService在第二步填充属性时发现又依赖了AService,于是尝试获取AService的Bean实例,一级缓存中没有,二级缓存中有,于是就将二级缓存中的“AService引用”拿过来填充属性值。这样循环就被打破了。

来画图说明一下

我用一个2×2的表格,来代表一级和二级缓存

两个Bean的创建流程如下图,我将两级缓存的内容放在时序图的左侧,你可以清楚的看到,两级缓存的内容是在什么时候发生变化的。

可以看到,如果只是简简单单的两个Bean相互依赖的话,引入一个存放半成品Bean的二级缓存就可以打破这个循环依赖了。

构建一个循环依赖(考虑AOP代理逻辑)

但是如果产生循环依赖的Bean同时又被AOP代理,需要产生代理对象的话,两级缓存就显得有点不够用了。

默认情况下,Spring将AOP产生代理对象的逻辑放在了“初始化Bean”这一步里,并且是放在了执行init方法之后,通过一段简略代码来看一下初始化Bean的流程

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {

    // 如果Bean实现了Aware接口,需要执行对应Aware的逻辑
	invokeAwareMethods(beanName, bean);

	Object wrappedBean = bean;
	
    // 执行Bean初始化前需要执行的前置操作
	wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, 

    // 执行init方法,初始化bean
	invokeInitMethods(beanName, wrappedBean, mbd);

	// 执行Bean初始化后需要执行的后置操作,代理对象就是在这一步生成
	wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, 

	return wrappedBean;
}

所以,Bean创建三步曲中,第一步实例化的Bean可能不是最终的Bean,如果这个Bean有代理逻辑的话,在第三步初始化Bean后,就产生了一个代理Bean,最终放入单例池中的也是这个代理Bean。

如何非要还用两级代理的话,能实现吗?

我觉得也能实现,但是要将AOP的代理逻辑往前提,提到第一步中,在实例化Bean之后,马上就执行代理逻辑,并且放入二级缓存里的也是代理后的对象。这样的话当循环依赖产生时,从二级缓存中也能直接获取到最终的代理对象。

但是这个做法貌似违背了Spring的设计初衷,Spring是将AOP的代理逻辑放在偏后的位置的,前面也说了,是放在执行了Bean对象的init方法后的。也就是说Spring的设计是:先将一个完整的Bean对象创建好,然后再看需不需要进行AOP代理。

那么Spring最终是怎么做的呢?Spring没有修改原来的设计,而是又加了一级缓存,也就是“三级缓存”,这第三级缓存就是用来处理AOP代理逻辑的。Spring允许当某个Bean产生循环依赖时提前执行其AOP代理逻辑,但是当没有循环依赖产生时,仍然是按部就班的先创建好Bean后,再尝试进行AOP代理。

Spring用一个Map来表示三级缓存,key是beanName,value为ObjectFactory对象。

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

这个ObjectFactory是何方神圣呢?看代码

它是一个接口,并且只有一个方法 getObject() ,这个接口上有一个@FunctionalInterface注解,这个注解用于指示一个接口是一个函数式接口。函数式接口是指仅包含一个抽象方法的接口,这样的接口可以用作 Lambda 表达式或方法引用的目标。而Spring也是采用了Lambda表达式的方式来使用这个接口。

来看两段源码

Spring往三级缓存中加对象的方法是这样定义的

调用 addSingletonFactory 这个方法的方式是这样的

可以看到调用处是用了一个Lambda表达式来表示 ObjectFactory 这个接口的具体实现。而getEarlyBeanReference这个方法则是代表了 ObjectFactory 接口中的 getObject() 方法。三级缓存就是调用这个方法来拿到一个半成品Bean的。

继续我们的代码和流程演示

这里我为上面的AService和BService加了一个AOP代理类

@Aspect
@Component
@Slf4j
public class MyAdvice {
    
    @Pointcut("execution(* com.sundries.circular_dependency.*Service.*(..))")
    public void pointCut(){}

    @Around("pointCut()")
    public Object aroundDeal(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = joinPoint.proceed();
        stopWatch.stop();
        long totalTimeMillis = stopWatch.getTotalTimeMillis();
        log.info("{} 方法执行耗时: {}ms", methodName, totalTimeMillis);
        return result;
    }
    
}

这个类里定义的pointCut就是指定了要切AService合BService两个类的所有方法,代理逻辑很简单,就是加了一个方法执行耗时的打印。

下面来看看使用三级缓存时,两个Bean的创建流程图。注意!这个流程已经是Spring的真实逻辑了。

Spring就是这样设计了一个三级缓存来解决了Bean的循环依赖问题。解决这个问题的核心思想就是提前将一个半成品的Bean引用给暴露出来,提供给其他Bean来作为属性值。

文末了,如果这里再问你一下:Spring是如何解决循环依赖的呢? 相信你已经能随口说出其中设计原理了👍🏻

下面是我根据Spring源码梳理这个流程时的过程图,感兴趣的话可以放大看下

不存在AOP代理时的情况

存在AOP代理时的情况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1956637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI副业赚钱免费资源大汇总

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;的热潮无处不在&#xff0c;许多人对于如何利用AI技术来创造收入机会感到好奇。本文将介绍一个名为aimoneyhunter的开源项目&#xff0c;这是一个专注于AI副业赚钱的资源汇总&#xff0c;旨在帮助人们在AI时代找…

技术干货 | 针对Spring-Boot 框架漏洞的初探

0x1 前言 这篇文章主要是给师傅们介绍下Spring-Boot 框架漏洞的打法以及主要对于Spring-Boot漏洞的接口泄露信息进行一个分析&#xff0c;后面使用了曾哥的Spring-Boot漏洞扫描工具&#xff0c;可以很大减轻我们对于这个漏洞接口的分析。 0x2 Spring框架简述 Spring是一个ja…

Ollama 在Windows Server 2019中部署 qwen:4b

1、环境 1.1、Ollama 1.2、Windows Server 2019 1.3、qwen:4b 1.4、nginx-1.27.0 2、部署后效果如下。 3、环境安装包下载链接包含本文所有的配置内容。 https://download.csdn.net/download/xingchengaiwei/89571623 4、安装ollama&#xff0c;官网下载地址Download Ollama…

【SpringCloud】企业认证、分布式事务,分布式锁方案落地-2

目录 高并发缓存三问 - 穿透 缓存穿透 概念 现象举例 解决方案 缓存穿透 - 预热架构 缓存穿透 - 布隆过滤器 布隆过滤器 布隆过滤器基本思想​编辑 了解 高并发缓存三问 - 击穿 缓存击穿 高并发缓存三问 - 雪崩 缓存雪崩 解决方案 总结 为什么要使用数据字典&…

一文带你了解RAG(检索增强生成) | 概念理论介绍+ 代码实操

一、LLMs 已经具备了较强能力了&#xff0c;为什么还需要 RAG(检索增强生成)? 尽管 LLM 已展现出显著的能力&#xff0c;但以下几个挑战依然值得关注&#xff1a; 幻觉问题&#xff1a;LLM 采用基于统计的概率方法逐词生成文本&#xff0c;这一机制内在地导致其可能出现看似逻…

PPT模板替换秘籍:一键撤销原模板,轻松更换新风格!

将PPT中的模板换成另一个模板&#xff0c;可以通过几种不同的方法实现。以下是几种常用的方法&#xff1a; 方法一&#xff1a;使用PowerPoint内置的设计选项卡 打开PowerPoint&#xff1a;首先&#xff0c;打开你想要更改模板的PPT文件。 选择“设计”选项卡&#xff1a;在…

Python设计模式 - 工厂方法模式

定义 工厂方法模式是一种创建型设计模式&#xff0c;它定义一个创建对象的接口&#xff0c;让其子类来处理对象的创建&#xff0c;而不是直接实例化对象。 结构 抽象工厂&#xff08;Factory&#xff09;&#xff1a;声明工厂方法&#xff0c;返回一个产品对象。具体工厂类都…

【学术会议征稿】2024年第七届机械工程与智能制造国际会议(WCMEIM 2024)

2024年第七届机械工程与智能制造国际会议&#xff08;WCMEIM 2024&#xff09; 2024 7th World Conference on Mechanical Engineering and Intelligent Manufacturing WCMEIM会议属一年一度的国际学术盛会。因其影响力及重要性&#xff0c;WCMEIM会议自创建筹办以来&#xff…

i 评论,网页评论插件使用示例

网页快速实现评论功能&#xff0c;i 评论插件&#xff0c;来试试吧&#xff01; https://andi.cn/page/621617.html

生成模型 VQVAE:Neural Discrete Representation Learning

注&#xff1a;加粗下划线名词详解见文章末 了解VQGAN之前&#xff0c;还学习了VQVAE&#xff08;Vector QuantisedVariational AutoEncoder&#xff09;&#xff09;这篇论文Neural Discrete Representation Learning&#xff0c;看了几个不错的学习视频 进行了深入了解 VQVAE…

搜狐视频的长期主义,让高精尖知识走近普罗大众

“如果你忽略了量子力学&#xff0c;就没有稳定的电磁系统。” “没有结构&#xff0c;我们就像灰烬一样。” 近日&#xff0c;在一场对谈中&#xff0c;张朝阳与美国哈佛大学教授、物理系系主任&#xff0c;美国国家科学院院士、狄拉克奖与基础物理学突破奖获得者库姆伦瓦法…

Matlab编程资源库(11)多项式计算

一、多项式的四则运算 1&#xff0e;多项式的加减运算 2&#xff0e;多项式乘法运算 函数conv(P1,P2)用于求多项式P1和P2的乘积。 这里&#xff0c;P1、P2是两个多项式系数向量。 3&#xff0e;多项式除法 函数[Q,r]deconv(P1,P2)用于对多项式P1和P2作除法运算。其中Q返回多项…

这么发sci论文,审稿人看了都流泪

前言 早上起来&#xff0c;忐忑的你打开审稿人的意见&#xff0c;看到这样一条评语&#xff0c;我们表述不够精准&#xff0c;口语化严重&#xff0c;学视性较弱。 你瞬间就清醒了&#xff0c;只能再次打开不知修改了多少遍的终稿&#xff0c;再次修改&#xff0c;心里想着&a…

魔法项链-小红书2024笔试(codefun2000)

题目链接 魔法项链-小红书2024笔试(codefun2000) 题目内容 你有一个魔法项链&#xff0c;现在你想要强化一下这件装备。你可以将魔法项链看做一条从头到尾串有 n 个不同魔力值宝石的绳子。根据你目前的冒险等级&#xff0c;你可以仅将其中的一颗宝石的魔力值强化并改变为 v 。…

基于OpenCV C++的网络实时视频流传输——Windows下使用TCP/IP编程原理

1.TCP/IP编程 1.1 概念 IP 是英文 Internet Protocol &#xff08;网络之间互连的协议&#xff09;的缩写&#xff0c;也就是为计算机网络相互连接进行通信而设计的协议。任一系统&#xff0c;只要遵守 IP协议就可以与因特网互连互通。 所谓IP地址就是给每个遵循tcp/ip协议连…

书生大模型实战营--L1关卡-Llamaindex RAG实践

一、安装llamaindex库 pip install llama-index pip install llama-index-embeddings-huggingface 二、问2024年巴黎奥运会 中国队获得几枚金牌&#xff0c;无法回答该问题 三、构建Llamaindex RAG 1、初始化llm 2、构建词向量模型 下载模型&#xff1a;git clone https://…

【论文阅读笔记】Lite-SAM Is Actually What You Need for Segment Everything

1.论文介绍 Lite-SAM Is Actually What You Need for Segment Everything Lite-SAM是您实际上所需的分割一切的工具 2024年 arxiv Paper 2.摘要 Segment Anything模型&#xff08;SAM&#xff09;以其优越的性能给分割领域带来了重大变化&#xff0c;但其对计算资源的巨大需…

设置浏览器ie兼容模式

点击设置 设置IE模式

VMware安装(有的时候启动就蓝屏建议换VM版本)

当你开始使用虚拟化技术来管理和运行多个操作系统时&#xff0c;VMware 是一个强大且广泛使用的选择。本篇博客将指导你如何安装 VMware Workstation Pro&#xff0c;这是一个功能强大的虚拟机软件&#xff0c;适用于个人和专业用户。 一、下载 VMware Workstation Pro 访问官网…

使用EasyAR打包安卓操作注意

EasyAR for Scene 4.6.3 丨Unity2020.3.15f2 打包Unity注意事项 一、默认渲染管线 官方参考链接&#xff1a;ARFoundation 简单注意 1.打包设置为Android平台 2.PackageName和EasyAR中保持一致 3.Scripting Backend设置为IL2CPP&#xff0c;以及设置为ARM64 4.取消Auto …