Spring循环依赖冤冤相报何时了

Spring循环依赖 🚎

    • 什么是循环依赖?
      • 那么循环依赖是个问题吗?
    • 单例setter, spring是如何解决的
      • 为什么需要三级缓存
        • 单单只有两级缓存行不行
        • 还得是你三级缓存
      • 三级缓存解决依赖循环全解

梦想不会逃跑,会逃跑的永远都是自己

什么是循环依赖?

// A依赖了B
class A{
public B b;
}

// B依赖了A
class B{
public A a;
}

那么循环依赖是个问题吗?

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。

但是spring中bean是有自己的一套生命周期, 正因为Bean的生命周期所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了(单例setter),而有的场景(构造器)则需要程序员来解决

如果对Bean的生命周期不甚了解, 可以先了解一下, 不然往下看怕是有点难受

单例setter, spring是如何解决的

下面说一下单例setter, spring是如何解决的

spring中通过三级缓存进行解决.

三级缓存的作用可以大概看一下, 大概记得有这么三个缓存先, 不急不急

三级缓存是通用的叫法。

  • 一级缓存为:singletonObjects

    • singletonObjects中缓存的是已经经历了完整生命周期的bean对象
  • 二级缓存为:earlySingletonObjects

    • earlySingletonObjects比singletonObjects多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects
  • 三级缓存为:singletonFactories

    • singletonFactories中缓存的是ObjectFactory,表示对象工厂,表示用来创建早期bean对象的工厂

为什么需要三级缓存

原本声明周期中就有一个缓存 singletonObjects 存放我们已经初始化后的对象, 有aop对象, 也有普通对象.

既然

单单只有两级缓存行不行

我先说一下我的思路, 有两个类A和B

首先我用一个缓存, 就叫它 singletonFactories 吧

缓存我们实例化后填充属性之前的原始对象

A 放进 singletonFactories 了

发现依赖 B, 实例化 B, 放进 singletonFactories,

好了发现依赖 A, 从 singletonFactories 拿到,

B 继续初始化, 完事

普通的依赖对象没问题, 可AOP呢, 我AOP怎么办

二级缓存表示不服, 我实例化后就直接创建AOP对象

但是有一个问题, 那就是不是所有类都需要AOP呀

这是后置处理器(初始化过程一环)干的活, 怎么让我一个干实例化的干初始化的活

我还要实例化的时候判断是否循环依赖了, 这倒是可以使用一个集合存放正在创建的类来判断

在这里创建AOP倒是有一个好处, 我可以直接拿到原始对象

PS: 动态代理需要用到原始对象, 当然Cglib也需要

既然太靠前了, 那我能不能等到真正等到真正循环依赖的时候就再进行AOP呢?

好了, 二个缓存都搞不定

还得是你三级缓存

AOP, 我三级缓存来救你了

OK, 我现在增加一个缓存, 叫做 earlySingletonObjects, 用来存放还没完全填充属性和初始化好的对象, 通常是普通对象也可以存放AOP对象

好, 继续说说 A 和 B

A 示例化好后, 我把原始对象放到三级缓存中,

A 填充属性, 发现依赖 B,

B 实例化, 发现依赖 A,

分别从一级, 二级, 三级,如果拿到一级/二级就返回,

如果拿到三级, 那我就创建二级对象, 并且放到二级缓存,

有好奇的同学就要问了, 那我二级缓存也能创建 AOP 对象呀

当然可以, 但是这样就创建了多个 AOP 对象,

二级缓存之后, 就算有多个对象依赖 A, 我们也可以拿到同一个 AOP 代理对象,

而不是多个

其实 Spring 第三级缓存里面放的不是原始的实例化好的对象,

而是 ObjectFactory

通过 lambda 表达式进行创建

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

PS: 该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator(AOP代理),一个是InstantiationAwareBeanPostProcessorAdapter

这样我们拿到 ObjectFactory 就能拿到当初的原始对象进行创建代理对象

AnnotationAwareAspectJAutoProxyCreator的父类就是AbstractAutoProxyCreator

AOP: 谢谢你, 我活下来了

ObjectFactory

使用ObjectFactory的方式不仅可以避免循环依赖问题,还可以为我们提供更多自定义实例化逻辑的机会。例如,我们可以使用ObjectFactory来创建代理对象、注入特定的依赖、在创建Bean实例之前或之后执行一些操作等等。

这里使用 ObjectFactory 而不直接把原始对象放置到三级缓存中, 是让我们可以做一些初始化的动作, 我们可以实现 SmartInstantiationAwareBeanPostProcessor
可以查看我的另一篇文章 spring5.1+SmartInstantiationAwareBeanPostProcessor 解决循环依赖_洪宏鸿的博客-CSDN博客

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

三级缓存解决依赖循环全解

首先,singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象 之后,就会构造一个ObjectFactory存入singletonFactories中。这个ObjectFactory是一个函数式接口,所以支持 Lambda表达式:()-> getEarlyBeanReference(beanName, mbd,bean)

上面的Lambda表达式就是一个ObjectFactory,执行该Lambda表达式就会去执行getEarlyBeanReference方法,而 该方法如下:

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;
}

该方法会去执行SmartInstantiationAwareBeanPostProcessor中的

getEarlyBeanReference方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator,一个是

InstantiationAwareBeanPostProcessorAdapter,它的实现如下:

// InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
	return bean;
}
// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}

在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了 getEarlyBeanReference方法,而该类就是用来进行AOP的。上文提到的

AnnotationAwareAspectJAutoProxyCreator的父类就是AbstractAutoProxyCreator。 那么getEarlyBeanReference方法到底在干什么?

首先得到一个cachekey,cachekey就是beanName。

然后把beanName和bean(这是原始对象)存入earlyProxyReferences中 调用wraplfNecessary进行AOP,得到一个代理对象。

那么,什么时候会调用getEarlyBeanReference方法呢?回到循环依赖的场景中

看下面文字

左边文字:

这个ObjectFactory就是上文说的labmda表达式,中间有getEarlyBeanReference方法,注意存入 singletonFactories时并不会执行lambda表达式,也就是不会执行getEarlyBeanReference方法

右边文字:

从singletonFactories根据beanName得到一个ObjectFactory,然后执行

ObjectFactory,也就是执行getEarlyBeanReference方法,此时会得到一个A原始对象 经过AOP之后的代理对象,然后把该代理对象放入earlySingletonObjects中,注意此时并没有把代理对象放入singletonObjects中,那什么时候放入到singletonObjects中 呢?

我们这个时候得来理解一下earlySingletonObjects的作用,此时,我们只得到了A原始对象的代理对象,这个对象还不完整,因为A原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入singletonObjects中,所以只能把代理对象放入

earlySingletonObjects,假设现在有其他对象依赖了A,那么则可以从

earlySingletonObjects中得到A原始对象的代理对象了,并且是A的同一个代理对象。

当B创建完了之后,A继续进行生命周期,而A在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道A原始对象已经经历过了AOP,所以对于A本身而言,不会再去进行AOP了,那么怎么判断一个对象是否经历过了AOP呢?会利用上文提到的earlyProxyReferences,在AbstractAutoProxyCreator的

postProcessAfterlnitialization方法中,会去判断当前beanName是否在

earlyProxyReferences,如果在则表示已经提前进行过AOP了,无需再次进行AOP。

对于A而言,进行了AOP的判断后,以及BeanPostProcessor的执行之后,就需要把A对应的对象放入singletonObjects中了,但是我们知道,应该是要把A的代理对象放入

singletonObjects中,所以此时需要从earlySingletonObjects中得到代理对象,然后入 singletonObjects中。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL逻辑架构

讲师:尚硅谷-宋红康(江湖人称:康师傅) 官网:http://www.atguigu.com 1. 逻辑架构剖析 1.1 服务器处理客户端请求 首先MySQL是典型的C/S架构,即Client/Server 架构,服务器端程序使用的mysqld…

【2023】Kubernetes之Pod与容器状态关系

目录简单创建一个podPod运行阶段:容器运行阶段简单创建一个pod apiVersion: v1 kind: pod metadata: name: nginx-pod spec:containers:- name: nginximages: nginx:1.20以上代码表示创建一个名为nginx-pod的pod资源对象。 Pod运行阶段: Pod创建后&am…

vba:end属性

1.从录制一个宏开始。操作:(ctrl向上键、ctrl向下键、ctrl向左键、ctrl向右键) 2.看代码 Range.End 属性 返回一个 Range 对象,该对象代表包含源区域的区域尾端的单元格。 等同于按键 (End向上键、End向下键、End向左键、End向右键) 语法 表达式.End (…

Java基础(十四):包装类

Java基础系列文章 Java基础(一):语言概述 Java基础(二):原码、反码、补码及进制之间的运算 Java基础(三):数据类型与进制 Java基础(四):逻辑运算符和位运算符 Java基础(六):数组 Java基础(七):面向对…

打包构建优化

最近沉迷逛某蓝色软件,收益良多!万分感谢博主 海阔_天空,写的太棒了👍🎉 下面是原文链接,我在原文的基础上浅做个笔记,方便个人快速复习 PS:本篇文章在评论区里存在很多吐槽技术太…

常见密码的编码

1.base64编码 字符包括字母A-Z、a-z、数字0-9,这样共有62个字符 当看到号的加密方式时,可以考虑base64。例:cTZ1NQ解码之后为q5u5。 bash32 只有大写字母(A-Z)和数字234567 bash64 只有数字0-9以及大写字母ABCDE…

Java 网络编程之NIO(Channel)

同步 VS 异步 同步 同步编程是指当程序执行某个操作时,它必须等待该操作完成才能继续执行下一个操作。这意味着程序在执行网络请求时必须等待网络请求完成才能继续执行下一步操作,因此同步编程通常会导致程序的性能降低。在网络编程中,同步编…

【分享】免梯子的GPT,玩 ChatGPT 的正确姿势

火了一周的 ChatGPT,HG 不允许还有小伙伴不知道这个东西是什么?简单来说就是,你可以让它扮演任何事物,据说已经有人用它开始了颜色文学创作。因为它太火了,所以,本周特推在几十个带有“chatgpt”的项目中选…

基于YOLOv5的水下海洋目标检测

摘要:水下海洋目标检测技术具有广泛的应用前景,可以用于海洋环境监测、海洋资源开发、海洋生物学研究等领域。本文提出了一种基于 YOLOv5 的水下海洋目标检测方法,使用数据增强方法进行了大量实验,并与其他方法进行了对比&#xf…

postgres 日志级别要设置成 wal_level = logical 的影响? 物理复制和逻辑复制

物理复制和逻辑复制 物理复制和逻辑复制 概念 WAL 区别 逻辑复制 将 PostgreSQL 的日志级别设置为 wal_level logical 会启用逻辑复制(logical replication)功能,这对于一些应用场景非常有用。 逻辑复制是一种高级的复制技术&#x…

Leetcode.1017 负二进制转换

题目链接 Leetcode.1017 负二进制转换 Rating : 1698 题目描述 给你一个整数 n,以二进制字符串的形式返回该整数的 负二进制(base -2)表示。 注意,除非字符串就是 "0",否则返回的字符串中不能含…

ClickHouse小记

1、ClickHouse简介 ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库(DBMS),使用 C 语言编写,主要用于在线分析处理查询(OLAP),能够使用 SQL 查询实时生成分析数据报 告。 官网&a…

该死,面试里怎么有那么多套路,我离字节只有一步之遥

自我介绍 不要想着简历上都有为什么还要自我介绍,这里主要考查的是求职者的表达能力和基本素质。重点体现自己的经历优势以及主动积极的态度。 从业时间,教育背景,工作经验,擅长技能,你的性格。 个人技能&#xff1a…

用BEM和现代CSS选择器控制级联

本文为 360 奇舞团前端工程师翻译原文标题:Taming the Cascade With BEM and Modern CSS Selectors原文作者:Liam Johnston原文地址:https://css-tricks.com/taming-the-cascade-with-bem-and-modern-css-selectors/BEM。就像前端开发领域的所…

免费且好用的ssh工具FinalShell的下载与安装

一、FinalShell介绍 1.1 特色功能 云端同步,免费海外服务器远程桌面加速,ssh加速,本地化命令输入框,支持自动补全,命令历史,自定义命令参数。 1.2 主要特性 1.多平台支持Windows,macOS,Linux; 2.多标签,批量服务器管理; 3.支持登录ssh和Windows远程桌…

页面预加载优化实践

概述在客户端开发中,列表类型页面大多都依赖网络请求,需要等网络数据请求下来后再刷新页面。但遇到网络请求慢的场景,就会导致页面加载很慢甚至加载失败。我负责会员的商品列表页面,在业务场景中,页面元素比较复杂&…

【从零开始学习 UVM】10.2、UVM TLM —— UVM TLM Blocking Put Port

文章目录 UVM TLM Port Example1. 创建一个发送器类,其端口类型为 uvm_blocking_put_port2. 创建一个接收器类,实现 put 方法。3. 在更高层次上连接端口及其实现Put Port 阻塞行为任何组件都可以通过 TLM put port向另一个组件发送事务。接收组件应该定义 put port的实现。这…

KDWS-24便携式六氟化硫气体微量水份测定仪

一、技术特点 (1)自校准:传感器探头可自动校准零点,自动消除因零点、漂移而引入的系统误差,保证每次测量的准确性,同时可免去每年校验的繁琐。 (2)快速省气:开机进入测量状态后每SF6气隔露点测定时间为2min左右。 (3)…

【动手学习深度学习笔记】

第二章预备知识 节省内存:使⽤切⽚表⽰法将操作的结果分配给先前分配的数组 Z[:] XY就可以把结果覆盖在Z的原内存上,而不是新开辟内存,就节省了内存了 处理缺失值 inputs,outputs data.iloc[:,0:2],data.iloc[:,2] # iloc为位置索引 in…

rk3568 Android 添加IR遥控器

rk3568 添加IR遥控器 生活中充满了各种波长的电磁波,所谓的可见(色)光就是人眼可见的电磁波谱,其波长为 380~770nm,为了避免遥控器发射的光造成人眼不适及减少一般人造光源干扰,故选用人眼不可见的红外线(Infrared)波长&#xff…