Spring为什么要用的三级缓存解决循环依赖

2023-11-20

一、代码准备

@Component("aService")
public class AService(){
	@Autowired
	private BService bService;
	
	public void test(){
        System.out.println(bService);
    }
}
@Component("bService")
public class BService(){
	@Autowired
	private AService aService;
}
@ComponentScan("com.test")
public class AppConfig {
}
public class Test {
    public static void main(String[] args){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        AService aService = (AService)applicationContext.getBean("AService");
        aService.test();
    }
}

在AService中依赖了BService,Bservice中依赖了AService。这种是通常认为会出现循环依赖的一种情况。
可以先执行下代码看下结果
在这里插入图片描述
正常打印出了BService

二、到底什么是循环依赖?

对于AService和BService来说,都是bean对象。
而要创建bean对象是有一定步骤的。

假设先创建AService,以下为创建bean对象的一些基本步骤
1.创建一个AService普通对象(new AService)
2.填充BService属性----》去单例池中获取BService—》找不到,回去创建一个BService的Bean对象—》执行BService的生命周期----》(和A相同),在填充AService属性时—》去单例池中获取AService—》找不到,回去创建一个AService的Bean对象—》执行AService的生命周期…
3.填充其他属性
4.其他操作
5.初始化后
6.放入单例池
进入了循环依赖

更详细的描述,可前往Spring循环依赖过程解析查看

三、三级缓存

第一级缓存:单例池
singletonObjects ConcurrentHasnMsp<beanName,bean对象>
作用:保证一个beanName对应唯一的Bean完整对象

第二级缓存
earlySingletonObjects HashMap<beanName,bean对象>
作用:保证一个beanName对应唯一的Bean不完整对象
属性暂时没有值的对象称之为不完整的Bean对象(还没有走完生命周期)
比如A、B、C三个类,B、C中依赖 A, A中依赖B、C
在创建A时,保证注入B C,创建时拿到的A的不完整对象是同一个。

第三级缓存
singletonFactory HashMap<beanName,ObjectFactory(lambda表达式)>
作用:做一些预备工作。创建bean的时候(第一步实例化产生的对象)先存到三级缓存,并不知道后面逻辑会不会用,会不会出现循环依赖等,只是防止出现循环依赖且AOP等场景。
三级缓存是真正打破循环的map

加了三级缓存之后,生命周期如下:

  1. creatingSet

  2. 实例化----->AService不完整对象(new AService())即原始对象—>第三级缓存<‘aSerivce’,lambda(AService原始对象,beanName,Beanefinition)>

  3. 填充bService属性—>从单例池中找bService—>创建bService

    bService的生命周期
    3.1 实例化…BService对象(new BService())
    3.2 填充aService属性—>从单例池中找aService—>找不到—>在cretaingSet中----->aService正在创建中—>aService出现了循环依赖—>从二级缓存中查找
          二级缓存找到对应的bean对象,直接拿来使用,继续向下执行,进行3.3
          二级缓存没有找到对应的bean对象----->—>提前AOP----->第三级缓存—>执行lambda—>得到代理对象—>放入第二级缓存<‘aSerivce’,AService代理对象>

    3.3 填充他属性
    3.4 做其他事情
    3.5 放入单例池

  4. 填充他属性

  5. 做其他事情

  6. 从二级缓存中取出AService对象

  7. 放入单例池

  8. creatingSet.remove(“aService”)

简单说:
先从单例池中查找,找到直接使用。
没有找到,从二级缓存中找,找到直接使用
没有找到,从三级缓存中找,执行lambda,得到对象放入二级缓存。

部分源码解释

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

从上述定义,可看出:一级缓存(单例池)使用的是ConcurrentHashMap,二级缓存和三级缓存都是用的HashMap

为什么二级缓存和三级缓存用HashMap而不是使用ConcurrentHashMap呢?难道就不考虑线程安全的问题吗?
由于三级缓存中存的是lambda表达式且是一次性的,只要执行过一次就会被移除。二级缓存中存的是三级缓存lambda执行的结果。

也就是说同一个bean的名字,在三级缓存中如果存在一个lambda表达式,那么就表示在二级缓存中beanName对应的就没有值。同样的,反过来,在二级缓存里面beanName有值,那么在三级缓存中就没有对应的表达式。相当于是原子性的。
从第二段代码中可以看出,在往二级缓存push的时候,会把三级缓存的数据清除掉,那么就必须保证操作的原子性。很显然,二级缓存和三级缓存定义为ConcurrentHashMap并不能保证操作的原子性。只能添加synchronize加锁控制。

结合第二三段代码,可以看到,两个map的操作总是在一起的,添加到一个里面就从另一个中移除,同时加锁控制,已经保证了并发的操作安全,所以就没有必要设置为ConcurrentHashMap,在这种前提下,考虑性能,选择了HashMap。
在这里插入图片描述

在比较新版本代码中,二级缓存换成了ConcurrentHashMap。因为在锁外面会用到二级缓存。原来的旧代码就还是HashMap

四、@Async为什么会导致循环依赖

加上异步注解,执行程序看下效果

@ComponentScan("com.test")
@EnableAspectJAutoProxy
@EnableAsync
public class AppConfig {
}
@Component
public class AService {
    @Autowired
    private BService bService;

    @Async
    public void test(){
        System.out.println(bService);
    }
}

运行之后发现报错:
在这里插入图片描述
Error creating bean with name ‘AService’: Bean with name ‘AService’ has been injected into other beans [BService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.
看报错是:循环依赖的问题。

注释掉异步注解,就可以正常执行了,这是为什么呢?
在这里插入图片描述
在Spring中@Async注解也有自己对应的切面逻辑
点击报错信息,可以看到是在这里抛出的异常
在这里插入图片描述

判断到AService循环依赖了,会在这里执行lambda表达式,这里执行的切面逻辑是自己自定义的逻辑
在这里插入图片描述
然后继续往下走:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
假设先执行的自定义切面的beanPostProcessor
在这里插入图片描述

然后再执行的@Aysnc的beanPostProcessor。并没有任何判断,直接设置处理了。
在这里插入图片描述
因为出现了循环依赖,AService在自定义切面逻辑里面已经生成了代理对象,进入下一次循环时,Async切面又会生成一个代理对象,而这两个代理对象肯定是不一样的。所以在后面抛出了异常
在这里插入图片描述

如果逻辑必须要这么处理,怎么来解决循环依赖呢?
可以使用@Lazy注解解决

@Component
public class AService {
    @Autowired
    private BService bService;

    @Async
    @Lazy
    public void test(){
        System.out.println(bService);
    }
}

加了@Lazy注解后;
      在创建AService的过程中,需要给属性BService赋值,直接赋值BService的代理对象,不管BService当前是什么情况,并不会从sprig容器中去找BService的bean对象而是直接生成BService的代理对象。
      在真正使用BService对象的时候才会去Spring容器中去找对应的bean对象。
      调用方法的时候,说明AService的生命周期已经执行完成,再创建BService的时候就不会出现循环依赖了。

五、构造方法和多例导致的循环依赖

构造方法

上面的代码,使用的都是默认的构造方法来生成的对象,如果是指定特定构造方法,会有什么问题呢?

比如没有用属性注入而是使用构造方法注入,以下代码:

@Component
public class AService {
    private BService bService;

   public AService(BService bService){
       this.bService = bService;
   }
    public void test(){
       System.out.println(this.bService);
   }
}
@Component
public class BService {

    private AService aService;

    public BService(AService aService){
        this.aService = aService;
    }
}

执行报错:
在这里插入图片描述
创建AService的bean对象,只能使用给出的构造方法,但是构造方法里面需要一个BService,发现找不到。
找不到就会去创建BService对象,也是只能使用给出的构造方法,需要一个AService的bean,不能再去创建AService了,但是又没有办法得到一个AService对象。

也就是说,在创建普通对象的时候就失败了,所有没有办法生成lambda表达式,更没有办法做其他事。

对应的解决办法也有,加上@Lazy注解即可。Spring会传入一个@Lazy对应的代理对象参数进入
在这里插入图片描述

多例

不论是AService、BService、AService+BService是原生bean,在创建使用指定构造方法的时候(使用上述的构造方法)
都会由于需要另一个bean对象而导致失败。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring为什么要用的三级缓存解决循环依赖 的相关文章

随机推荐

  • jest搭建vue项目单元测试-现有老项目

    说到项目会分为新建的醒目和老项目两种 jest搭建vue项目单元测试 vue cli创建新项目 我们接下来说现有老项目 现有的vue老项目或者没使用vue cli创建项目搭建jset单元测试 1 安装 npm i vue test util
  • 重启Vcenter命令

    重启Vcenter命令 通过ssh登录Vcenter 输入root 输入shell 输入service control stop all 输入service control start all 停止 启动或重新启动 VMware vCent
  • linux xenserver教程,XenServer常用命令

    监控检查类 xentop 查看XenServer与VM的资源使用情况 xsconsole 进入XenServer管理面板 查看网卡 IP 系统版本 系统时间 硬件信息等 xe task list 查看XenServer临时任务进程 serv
  • 【C语言】验证哥德巴赫猜想

    文章目录 问题来源 题目要求 如何判断素数 主函数 完整代码 效果演示 写代码中的误解 总结 问题来源 这是学校的一个作业 原题如下 题目先给出了哥德巴赫猜想的背景知识 我还真不知道 2000以内的正偶数都能分解成两个质数 素数 之和 题目
  • 毕业设计-基于 PID 控制算法仿真算法研究- Matlab

    目录 前言 课题背景和意义 实现技术思路 一 基本原理 二 无超调 PID 控制器的设计 三 无超调 PID 设计的验证 代码 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一
  • 安装anaconda及修改conda config 的channels/default_channels

    先说一下安装anaconda的方法 很简单 就是去官网下载然后在本地安装 bash Anaconda3 4 4 0 Linux x86 64 sh 这个过程中要耐心 会有提问 需要输入yes来回应 并且需要按很多的回车 总之 看见让输入ye
  • 转:机器学习的理解

    转李航博士的一篇关于机器学习理解的文章 算算时间 从开始到现在 做机器学习算法也将近八个月了 虽然还没有达到融会贯通的地步 但至少在熟悉了算法的流程后 我在算法的选择和创造能力上有了不小的提升 实话说 机器学习很难 非常难 要做到完全了解算
  • Ridis持久化

    Redis持久化 RDB Redis DataBase Redis会单独创建 fork 一个子进程来进行持久化 会先将数据写入到一个临时文件中 待持久化都结束了 再用这个临时文件替换上次持久化好的文件 整个过程中 主进程是不进行io操作的
  • 8--UI 初步认识 简易计算器

    UI是App的根基 一个App应该是先有UI界面 然后在UI的基础上增加实用功能 2 UI相对简单易学 UI普遍是学习过程中最简单的一块 能快速拥有成就感和学习兴趣 3 UI至关重要 开发中的绝大部分时间都在处理UI 谨记一条IOS软件开发
  • MySQL根据某一个或者多个字段查找重复数据

    sql 查出一张表中重复的所有记录数据 1 表中有id和name 两个字段 查询出name重复的所有数据 select from xi a where a username in select username from xi group
  • 系列教程

    PDF Search 系列教程来咯 在 Part 1 中 我们将演示如何从 PDF 中提取 处理并存储图像及文本 随着神经搜索 Neural Search 技术的普及 越来越多开发者 开始尝试用 Jina 解决非结构化数据的索引和搜索问题
  • MySQL必知必会 学习笔记 第二十五章 使用触发器

    触发器在MySQL 5中增加 触发器可以在MySQL响应DELETE INSERT UPDATE语句时自动执行一条SQL语句 MySQL 5中触发器名在每个表中唯一而不是在一个数据库中唯一 其他DBMS有的重名限制是数据库范围 以后MySQ
  • lua和测试(一)

    lua做为一门高级语言 在游戏产业运用到机会越来越多了 测试掌握几门脚本语言也有一定的重要性 以下对于lua组合输入做出一些引导 测试需要掌握的关于返回数值 主要用到布尔类 前言的指引 lua的语法比较简单和清晰 学过c语言的可以很好的掌握
  • 并发编程系列之自定义线程池

    前言 前面我们在讲并发工具类的时候 多次提到线程池 今天我们就来走进线程池的旅地 首先我们先不讲线程池框架Executors 我们今天先来介绍如何自己定义一个线程池 是不是已经迫不及待了 那么就让我们开启今天的旅途吧 什么是线程池 线程池可
  • selenium+python 对输入框的输入处理

    最近自己在做项目的自动化测试 公司无此要求 在用户管理模块做修改用户信息时 脚本已经跑成功 并且的确做了update操作 但是自己登陆页面检查 信息却没有被修改 再次确定系统该模块的编辑功能可用 脚本如下 if result num gt
  • 近千万EOS被盗事件回顾,大家请保护好自己的EOS私钥

    最近有伙伴被盗了价值近千万的EOS 于是查看了这次被盗活动账号记录 这次分享出来 一是有可能大家有线索 二是也让大家意识到数字货币私钥安全的重要性 事件回顾 受害人在7 9号被偷盗人通过update auth更换了账号授权公私钥 紧接着被转
  • 零基础到GPT高手:快速学习与利用ChatGPT的完全指南

    进入人工智能时代 令人惊叹的ChatGPT技术正在引爆全球 您是否想象过能够与智能语言模型对话 提升工作效率 解锁创意 甚至实现商业化变现 在本篇文章中 我将向你揭示ChatGPT的原理 学习技巧 并展示如何利用ChatGPT提升工作效率和
  • Windows11:QT5.14.2+PCL1.12.0+VS2019环境配置

    之前在win10系统下配置了PCL1 8 1 QT5 9 1 VS2015的开发环境 由于PCL库已经更新到了1 12 1而且1 8 1一直有bug 为了使用下新的算法库 今天配置一下新的开发环境 1 安装Qt5 14 2 Qt5 14 2
  • 【b站雅思笔记】Simon‘s IELTS Course - 听力部分

    前情提要 b站up主贼开心的小林上传的Simon的听力课 资料均来源于她 参考 雅思阅读 最好的雅思课程 阅读部分全集 https www bilibili com video BV1ea4y1x7qR spm id from 333 78
  • Spring为什么要用的三级缓存解决循环依赖

    一 代码准备 Component aService public class AService Autowired private BService bService public void test System out println