Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?

首先,我将简单介绍一下Spring框架中的动态代理和循环依赖问题。

动态代理与循环依赖

1. 动态代理

在Spring框架中,动态代理是一种常用的技术,用于实现AOP(面向切面编程)。动态代理允许Spring在运行时为目标对象创建一个代理,以此来插入额外的逻辑,例如事务管理、日志记录等。

2. 循环依赖

循环依赖是指两个或多个Bean相互依赖,形成闭环,导致无法顺利完成依赖注入。例如,Bean A依赖Bean B,而Bean B又依赖Bean A。

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

解决循环依赖:三级缓存

1. 三级缓存

Spring使用三级缓存解决循环依赖的问题:

  • 一级缓存:存放完全初始化完成的Bean(单例池)。
  • 二级缓存:存放原始Bean的早期引用。
  • 三级缓存:存放Bean的ObjectFactory,用于生成Bean的代理对象。

2. 工作机制

当Spring容器创建Bean时,首先将实例化后的原始Bean放入三级缓存。如果在Bean的完全初始化之前需要引用该Bean,Spring会通过三级缓存中的ObjectFactory来创建Bean的代理对象,并将其提升到二级缓存中。这样,即使在Bean还未完全初始化之前,也能通过代理对象来解决循环依赖的问题。

使用场景与性能优化

1. 使用场景

  • 事务管理:使用动态代理管理事务边界。
  • 日志记录:在方法执行前后添加日志记录。
  • 安全性:在方法调用前进行权限检查。

2. 性能优化

  • 减少代理创建:仅对关键服务进行代理,避免过度使用动态代理。
  • 懒加载:适当使用懒加载,延迟Bean的初始化。

代码示例

让我们通过一个简单的例子来理解Spring的动态代理和循环依赖的处理:

// 服务接口
public interface UserService {void addUser(String username);
}// 实现类
public class UserServiceImpl implements UserService {@Autowiredprivate OrderService orderService; // 依赖OrderService@Overridepublic void addUser(String username) {System.out.println("Adding user: " + username);// ... 其他逻辑 ...}
}// 另一个服务
public class OrderService {@Autowiredprivate UserService userService; // 依赖UserServicepublic void createOrder(String username) {System.out.println("Creating order for: " + username);// ... 其他逻辑 ...}
}

在这个例子中,UserService 和 OrderService 互相依赖。Spring通过三级缓存机制,确保这种循环依赖不会导致问题。

Spring中三级缓存机制是如何工作的,特别是在解决循环依赖的情况下。

我会用一个稍微复杂点的例子来展示这一机制。

场景设定

假设我们有两个组件,A 和 B,它们互相依赖。为了简化示例,我们假设它们都是单例(Spring默认的作用域)。

@Component
public class A {@Autowiredprivate B b;public A() {System.out.println("A 创建");}// A的其他方法...
}@Component
public class B {@Autowiredprivate A a;public B() {System.out.println("B 创建");}// B的其他方法...
}

三级缓存的工作原理

实例化

当Spring容器启动时,它会尝试创建这些Bean。

首先,它创建了A的实例。在这个过程中,Spring发现A需要依赖B。

然后,它开始创建B的实例。同样,在创建B时,发现B需要依赖A。

三级缓存介入

此时,A的实例已经被部分创建,并存放在三级缓存中。

当Spring为B创建依赖A时,它不会重新创建A的实例。相反,它会使用存在于三级缓存中的A的早期引用。

这个早期引用足以满足B对A的依赖,从而允许B的创建过程继续。

依赖注入与完成创建

一旦B被成功创建,Spring会完成对A的依赖注入。

这时,A和B都已经被完全创建,并存放在Spring的一级缓存(即单例池)中。

关键点

这个过程中的“早期引用”通常是通过使用ObjectFactory创建的代理对象。

三级缓存主要用于解决这种循环依赖的问题,同时也确保了Spring容器的线程安全。

注意事项

虽然三级缓存机制很强大,但它仅适用于单例作用域的Bean。

对于原型作用域的Bean,Spring不会尝试解决循环依赖,这可能会导致BeanCurrentlyInCreationException异常。

推荐一个学习 Spring源码分析 的专栏文章

  • 01、Spring源码分析 - 01-DispatcherServlet注册过程
  • 02、Spring源码分析 - 02-Resource
  • 03、Spring源码分析 - 03-ResourceLoader
  • 04、Spring源码分析 - 04-类型转换
  • 05、Spring源码分析 - 05-字段格式化
  • 06、Spring源码分析 - 06-ResolvableType
  • 07、Spring源码分析 - 07-BeanWrapper
  • 08、Spring源码分析 - 08-DataBinder
  • 09、Spring源码分析 - 09-PropertySourcesPropertyResolver
  • 10、Spring源码分析 - 10-Environment
  • 11、Spring源码分析 - 11-BeanFactory的实现
  • 12、Spring源码分析 - 12-BeanFactory创建Bean的重要流程图
  • 13、Spring源码分析 - 13-DispatcherServlet中WebApplicationContext启动过程
  • 14、Spring源码分析 - 14-Spring默认重要的组件
  • 15、Spring源码分析 - 15-ConfigurationClassPostProcessor
  • 16、Spring源码分析 - 16-AutowiredAnnotationBeanPostProcessor
  • 17、Spring源码分析 - 17-RequiredAnnotationBeanPostProcessor
  • 18、Spring源码分析 - 18-CommonAnnotationBeanPostProcessor
  • 19、Spring源码分析 - 19-ConfigurationClassPostProcessor
  • 20、Spring源码分析 - 20-Spring事件/监听器机制
  • 21、Spring源码分析 - 21-Spring AOP概述
  • 22、Spring源码分析 - 22-Spring AOP的实现原理之ProxyFactoryBean
  • 23、Spring源码分析 - 23-TargetSource目标源
  • 24、Spring源码分析 - 24-基于注解@Aspect的AOP实现
  • 25、Spring源码分析 - 25-Spring异步实现原理
  • 26、Spring源码分析 - 26-TaskExecutor与TaskScheduler
  • 27、Spring源码分析 - 27-基于注解@Scheduled定时任务实现
  • 28、Spring源码分析 - 28-Spring缓存原理详解
  • 29、Spring源码分析 - 29-JdbcTemplat的设计与实现
  • 30、Spring源码分析 - 30-Spring编程式事物的设计与实现
  • 31、Spring源码分析 - 31-Spring声明式事物的设计与实现
  • 32、Spring源码分析 - 32-基于注解@Transactional的事物实现
  • 33、Spring源码分析 - 32-基于注解@Transactional的事物实现
  • 34、Spring源码分析 - 34-Spring Bean作用域的设计与实现
  • 35、Spring源码分析 - 35-Spring MVC设计原理
  • 36、Spring源码分析 - 36-Spring MVC参数值的绑定
  • 37、Spring源码分析 - 37-Spring MVC的异常处理
  • 38、Spring源码分析 - 38-RestTemplate详解
  • 39、Spring源码分析 - 39-Spring容器生命周期回调接口LifeCycle
  • 40、Spring源码分析 - 40-Spring Validation参数校验的使用与原理
  • 41、Spring源码分析 - 41-ClassPathBeanDefinitionScanner
  • 42、Spring源码分析 - 42-@Conditional详解

真实案例比较

在实际开发中,我们通常会遇到以下几种情况:

  1. 简单的CRUD操作:通常不需要动态代理。
  2. 复杂的业务逻辑:涉及事务、安全性检查时,动态代理非常有用。
  3. 高性能要求的场景:在这种场景下,过度使用动态代理

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

项目文档&视频:

项目文档 & 视频

本文,已收录于,我的技术网站 ddkk.com,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激

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

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

相关文章

C++『异常』

✨个人主页: 北 海 🎉所属专栏: C修行之路 🎃操作环境: Visual Studio 2022 版本 17.6.5 文章目录 🌇前言🏙️正文1.异常基本概念1.1.C语言异常处理方式1.2.C异常处理方式 2.异常的使用2.1.异常…

在线网页生成工具GrapesJS

项目地址 https://github.com/GrapesJS/grapesjshttps://github.com/GrapesJS/grapesjs 项目简述 这是一个基于node.js的在线网页生成项目,对简化开发有很大的帮助。 主要使用的语言如下: 编辑页面如下: 使用也很简洁 具体可以看下项目。…

使用c++编程语言,将字符串中的数字全部替换成字符串:number

给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 样例输入:a1b2c3 样例输出:anumberbnumbercnumber 代码如下: #incl…

12. MySQL 锁机制

目录 概述 MylSAM引擎 InnoDB引擎 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资如何保证数据…

Pytest中使用Fixture替换Unittest的Setupclass及Pytest使用装饰器应用参数化

1 类里使用Fixture Pytest中夹具(Fixture)有几种生命周期:function->model->class->session->packages,其中默认为function。 import pytest from Common.logger import Log from Common.Operator import * fro…

C#中的Attributes特性创建和结合反射应用举例

C#中的特性入门学习 Attributes官方介绍概述 Attributes provide a powerful method of associating metadata, or declarative information, with code (assemblies, types, methods, properties, and so forth). After an attribute is associated with a program entity, …

深入理解Vue.js中的this:解析this关键字及其使用场景

在Vue.js中,this 和 that 可能是指向不同对象的两个变量,或者是在代码中使用时的错误。 this: 在Vue组件中,this 指向当前组件的实例。可以通过 this 访问组件的属性和方法。 例如,在Vue组件的 data 属性中定义了一…

2023年第十届GIAC全球互联网架构大会-核心PPT资料下载

一、峰会简介 谈到一个应用,我们首先考虑的是运行这个应用所需要的系统资源。其次,是关于应用自身的架构模式。最后,还需要从软件工程的不同角度来考虑应用的设计、开发、部署、运维等。架构设计对应用有着深远的影响,它的好坏决…

Leetcode659. 分割数组为连续子序列

Every day a Leetcode 题目来源:659. 分割数组为连续子序列 解法1:哈希 贪心 定义两个哈希表: numsCount:统计数组 nums 中各元素出现次数。tailCount:存储以数字 i 结尾的且符合题意的连续子序列个数。 算法&a…

极兔单号查询,极兔快递物流查询,一键筛选出退回件

批量查询极兔快递单号的物流信息,一键筛选出其中的退回件。 所需工具: 一个【快递批量查询高手】软件 极兔快递单号若干 操作步骤: 步骤1:运行【快递批量查询高手】软件,并登录 步骤2:点击主界面左上角的…

【Bootloader学习理解----跳转优化异常】

笔者接着来介绍一下Bootloader的跳转代码以及优化 1、跳转代码理解 跳转代码可能要涉及到芯片架构的知识,要跳转到对应的位置,还要设置相关的SP 堆栈指针,具体可以参考笔者这篇文章BootLoader的理解与实现。 STM32的跳转代码如下所示: u32 …

ClickHouse为何如此之快

针对ClickHose为什么很快的问题,基于对ClickHouse的基础概念之上,一般会回答是因为是列式存储数据库,同时也会说是使用了向量化引擎,所以快。上面两方面的解释也都能够站得住脚,但是依然不能够解释真正核心的原因。因为…

AI:101-基于深度学习的航空影像中建筑物识别

🚀 本文选自专栏:人工智能领域200例教程专栏 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的核心代码,详细讲解供大家学习,希望可以帮到大家。欢迎订阅支持,正在不断更新…

2023_刷题_二叉树

文章目录 书leixingleixing 书 leixing leixing

基于以太坊的智能合约开发Solidity(基础篇)

参考教程:基于以太坊的智能合约开发教程【Solidity】_哔哩哔哩_bilibili 1、第一个程序——Helloworld: //声明版本号(程序中的版本号要和编译器版本号一致) pragma solidity ^0.5.17; //合约 contract HelloWorld {//合约属性变…

Python轴承故障诊断 (四)基于EMD-CNN的故障分类

目录 前言 1 经验模态分解EMD的Python示例 2 轴承故障数据的预处理 2.1 导入数据 2.2 制作数据集和对应标签 2.3 故障数据的EMD分解可视化 2.4 故障数据的EMD分解预处理 3 基于EMD-CNN的轴承故障诊断分类 3.1 训练数据、测试数据分组,数据分batch 3.2 定义…

D : DS查找——折半查找求平方根

Description 假定输入y是整数&#xff0c;我们用折半查找来找这个平方根。在从0到y之间必定有一个取值是y的平方根&#xff0c;如果我们查找的数x比y的平方根小&#xff0c;则x2<y&#xff0c;如果我们查找的数x比y的平方根大&#xff0c;则x2>y&#xff0c;我们可以据此…

stu05-前端的几种常用开发工具

前端的开发工具有很多&#xff0c;可以说有几十种&#xff0c;包括记事本都可以作为前端的开发工具。下面推荐的是常用的几种前端开发工具。 1.DCloud HBuilder&#xff08;轻量级&#xff09; HBuilder是DCloud&#xff08;数字天堂&#xff09;推出的一款支持HTML5的web开发…

硬件开发笔记(十四):RK3568底板电路LVDS模块、MIPI模块电路分析、LVDS硬件接口、MIPI硬件接口详解

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/134634186 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

linux 关于$-的解释(帖子搜索合集)

在学习Linux的时候&#xff0c;今天遇到了$-&#xff0c;什么意思呢&#xff1f;网上搜索了一些帖子&#xff1a; 帖子1&#xff1a; linux命令 $- 是什么意思 $- 是什么意思&#xff1f;有什么用&#xff1f;可以判断什么交互式shell&#xff1f; $-记录着当前设置的shell…