Spring循环依赖代码演示及解决方案
作者:.番茄炒蛋 发布时间:2022-05-17 01:56:08
介绍
上图就是循环依赖的三种情况,虽然方式不同,但是循环依赖的本质是一样的,就A的完整创建要依赖与B,B的完整创建要依赖于A,相互依赖导致没办法完整创建造成失败.
循环依赖代码演示
public class Demo {
public static void main(String[] args) {
new Demo1();
}
}
class Demo1{
private Demo2 demo2 = new Demo2();
}
class Demo2 {
private Demo1 demo1 = new Demo1();
}
上述代码就是最基本的循环依赖的场景,Demo1依赖Demo2,Demo2依赖Demo1,然后就报错了,而上面的这种设计情况是无解的.
分析问题
首先我们要明确一点就是如果这个对象A还没创建成功,在创建的过程中要依赖另一个对象B,而另一个对象B也是在创建中要依赖对象A,这种肯定是无解的,这时我们就要缓缓思路,我们先把A创建出来,但是还没有完成初始化操作,也就是这是一个半成品对象,然后再赋值的时候提前把A暴露出来,然后创建B,让B创建完成后找到暴露出来的A完成整体的实例化,这时再把B交给A完成A的后续操作.从而解决循环依赖,也就是下图:
代码解决
public class Demo {
/**
* 保存提前暴露的对象,也就是半成品对象
*/
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
public static void main(String[] args) throws Exception {
System.out.println(getBean(Demo1.class).getDemo2());
System.out.println(getBean(Demo2.class).getDemo1());
}
private static <T> T getBean(Class<T> clazz) throws Exception {
// 获取beanName
String beanName = clazz.getName().toLowerCase();
// 查找缓存中是否存在半成品对象
if (singletonObjects.containsKey(beanName)) {
return (T) singletonObjects.get(beanName);
}
// 缓存中不存在半成品对象,反射进行实例化
T res = clazz.newInstance();
// 将实例化后的对象储存到缓存
singletonObjects.put(beanName, res);
// 获取所有属性
Field[] fields = res.getClass().getDeclaredFields();
// 循环进行属性填充
for (Field field : fields) {
// 针对private修饰
field.setAccessible(Boolean.TRUE);
// 获取属性类型
Class<?> fieldClazz = field.getType();
// 获取属性beanName
String filedBeanName = fieldClazz.getName().toLowerCase();
// 属性填充,查找缓存是否有对应属性,没有就递归调用
field.set(res, singletonObjects.containsKey(filedBeanName) ? singletonObjects.get(filedBeanName) : getBean(fieldClazz));
}
return res;
}
}
class Demo1 {
private Demo2 demo2;
public Demo2 getDemo2() {
return demo2;
}
public void setDemo2(Demo2 demo2) {
this.demo2 = demo2;
}
}
class Demo2 {
private Demo1 demo1;
public Demo1 getDemo1() {
return demo1;
}
public void setDemo1(Demo1 demo1) {
this.demo1 = demo1;
}
}
在上面的方法中核心就是getBean方法,Demo1创建后填充属性时依赖Demo2,那么就去创建Demo2,在创建Demo2开始填充时发现依赖Demo1,但此时Demo1这个半成品对象已经放在缓存singletonObjects中了,所以Demo2正常创建,再结束递归把Demo1也创建完整了.
Spring循环依赖
针对Spring中Bean对象的各种场景,支持的方案不一样
单例
构造注入:无解,避免栈溢出,需要检测是否存在循环依赖的情况,如果有直接抛异常
设值注入: * 缓存–>提前暴露
原型
构造注入:无解,避免栈溢出,需要检测是否存在循环依赖的情况,如果有直接抛异常
设置注入:不支持循环依赖
Spring是如何解决循环依赖问题的?上述代码中对象的生命周期就两个:创建对象和属性填充,而Spring涉及到对象生命周期的方法就很多了,简单举例,如下图:
基于对上述代码的了解,我们知道肯定需要在调用构造方法创建完成后再暴露对象,再Spring中提供了 * 缓存来处理这个事情,如下图:
对应到源码中具体处理循环依赖的流程如下:
上面就是Spring的生命周期方法和循环依赖出现相关的流程了.下面就是放入 * 缓存的源码:
/**
* 添加对象到 * 缓存
*
* @param beanName
* @param singletonFactory
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
// 确保singletonFactory不为null
Assert.notNull(singletonFactory, "Singleton factory must not be null");
// 使用singletonObjects进行加锁,保证线程安全
synchronized (this.singletonObjects) {
//如果singletonObjects缓存中没有该对象
if (!this.singletonObjects.containsKey(beanName)) {
// 将对象放置到singletonFactories( * 缓存)中
this.singletonFactories.put(beanName, singletonFactory);
// 从earlySingletonObjects(二级缓存)中移除该对象
this.earlySingletonObjects.remove(beanName);
// 将beanName添加到已经注册的单例集中
this.registeredSingletons.add(beanName);
}
}
}
放入二级缓存的源码:
/**
* 返回在给定名称 * 册的(原始)单例对象.检查已经实例化的单例,并允许对当前创建的单例进行早期引用(解决循环引用)
*
* @param beanName
* @param allowEarlyReference
* @return
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 不需要完全获取单例锁的情况下快速检查现有实例
Object singletonObject = this.singletonObjects.get(beanName);
// 如果单例对象为空,并且当前单例正在创建中,则尝试获取早期单例对象
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果早期单例对象为空,并且允许早期引用,则再完全获取单力所的情况下创建早期单例对象
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 检查早期单例对象是否存在
singletonObject = this.singletonObjects.get(beanName);
// 如果早期对象仍然为空则创建单例对象
if (singletonObject == null) {
// 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 获取不到对象从 * 缓存中获取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 获取到添加到二级缓存并从 * 缓存中移除该对象
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
放入一级缓存中的源码:
/**
* 将单例对象添加到一级缓存
*
* @param beanName
* @param singletonObject
*/
protected void addSingleton(String beanName, Object singletonObject) {
// 使用singletonObjects进行加锁,保证线程安全
synchronized (this.singletonObjects) {
// 将映射关系添加到一级缓存
this.singletonObjects.put(beanName, singletonObject);
// 从 * 缓存;二级缓存中移除该对象
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
// 将beanName添加到已经注册的单例集中
this.registeredSingletons.add(beanName);
}
}
来源:https://blog.csdn.net/qq_43135259/article/details/130134970
猜你喜欢
- 单例模式算是设计模式中最容易理解,也是最容易手写代码的模式,但是其中涉及的知识点却一点也不少,所以经常作为面试题来考。一般单例都是五种写法:
- 面向对象有封装、继承、多态这三个特性,面向对象编程按照现实世界的特点来管理复杂的事物,把它们抽象为对象,具有自己的状态和行为,通过对消息的反
- Task的应用Task的MSDN的描述如下:【Task类的表示单个操作不会返回一个值,通常以异步方式执行。Task对象是一种的中心思想基于任
- 前言最近在给熔断器组件增加一个降级策略(Hystrix好像没有这个配置),我们提供了如下几种策略:1、默认策略2、返回常量值3、抛出指定异常
- bean 的生命周期对象创建实例化Bean对象,默认选择无参构造方法,如果只有一个有参构造那么调用有参构造,如果只有多个有参构造那么报错,除
- RandomAccessFileRandomAccessFile 是随机访问文件(包括读/写)的类。它支持对文件随机访问的读取和写入,即我们
- 想必我们在做项目的时候,都会遇到服务端与客户端交互数据。一般情况下我们都会采用json格式或者xml格式,将服务端的数据转换成这两种格式之一
- 一、运算符用于创建对象和调用构造函数。这种大家都比较熟悉,没什么好说的了。二、修饰符在用作修饰符时,new 关键字可以显式隐藏从基类继承的成
- Java Collection API提供了一些列的类和接口来帮助我们存储和管理对象集合。其实Java中的集合工作起来像是一个数组,不过集合
- Java注解的Excel导出依赖: <dependency> &
- 前言对于字符串的操作,我们常用的就是trim()去除前后空格、subString()截取子字符串,其他的用的不多。下表中是字符串常用的方法。
- 1、导入资源2、JSP代码<div class="page-container">  
- 题目:使用栈计算类似表达式:5+2*3-2 的计算结果 提示:简易计算器操作符号限于+,-,*,/的计算分析思路:1、
- 1 需求Mybatis-plus使用@TableLogic注解进行逻辑删除数据后,在某些场景下,又需要查询该数据时,又不想写SQ
- 文件上传也是常见的功能,趁着周末,用Spring boot来实现一遍。前端部分前端使用jQuery,这部分并不复杂,jQuery可以读取表单
- 本来就是基础知识,不能丢的太干净,今天竟然花了那么长的时间才写出来,记一下。有如下的一颗完全二叉树:先序遍历结果应该为:1 2&
- 子类重新实现父类的方法称重写;重写时可以修改访问权限修饰符和返回值,方法名和参数类型及个数都不可以修改;仅当返回值为类类型时,重写的方法才可
- 一、根据流向分为输入流和输出流:注意输入流和输出流是相对于程序而言的。输出:把程序(内存)中的内容输出到磁盘、光盘等存储设备中输入:读取外部
- 1:和junit一起使用的时候因为没有读取配置文件,所以老是报创建Bean失败,上网查了查,原来是先要读取spring的核心配置文件,这样机
- 这篇文章主要介绍了Java解析json报文实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可