Spring03-IOC-循环依赖的实现(Debug查看spring解决循环依赖的过程)
1 什么是循环依赖
如下,有类A和B,A中有一个类型为B的属性b,B中有一个类型为A的属性a,A和B相互依赖
public class A { private B b; public B getB() {
return b;
} public void setB(B b) {
this.b = b;
} public A() {
} public A(B b) {
this.b = b;
}
}
public class B { private A a; public A getA() {
return a;
} public void setA(A a) {
this.a = a;
} public B() {
} public B(A a) {
this.a = a;
}
}
public static void main(String[] args) {
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
}
通常,属性的设置可以通过set方法和构造器来完成。我们可以通过set方法来设置。
2 spring解决循环依赖的方法概述
如下图,如果Spring要创建A和B并且对a和b属性赋值,像这样做就陷入了一个死循环。
那么,spring怎么去解开这个环呢?
spring创建对象是分为两大步的,实例化和初始化。
所以,可以先把实例化的A、B先存起来,等需要使用A、B的时候拿过来用。
如下图:我们增加一个缓存。把实例化的对象放进去,要用的时候拿出来就可以了。这样子,这个死循环就解开了。
所以,spring解开循环依赖的问题的关键点就在:实例化和初始化分开在做+缓存完成实例化但未初始化的对象(半成品对象)。
spring的缓存共采用了三级缓存
3 spring三级缓存源码(DefaultSingletonBeanRegistry.java)
我们先看一下三级缓存,他们都是Map。
一级缓存的value是Object
二级缓存的value是Object
三级缓存的value是ObjectFactory
我们先记住这三个缓存的名称和value
/** Cache of singleton objects: bean name to bean instance. */
//一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */
//三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */
//二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
不考虑动态代理的话,采用二级缓存就可以解决循环依赖的问题,因为考虑到动态代理的额问题,所以采用了三级缓存
4.接下来采用Bebug来看spring解决循环依赖的具体实现
接下来采用Bebug来看spring解决循环依赖的具体实现。在这之前,我们需要先了解spring创建对象的过程:https://www.cnblogs.com/jthr/p/15910423.html
下图我列出创建对象中主要的几个方法,我们主要看这几个方法
5 测试准备
5.1 xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"> <bean id="a" class="com.ruoyi.weixin.user.Test.A">
<property name="b" ref="b"></property>
</bean> <bean id="b" class="com.ruoyi.weixin.user.Test.B">
<property name="a" ref="a"></property>
</bean>
</beans>
5.2 测试代码
public class CycleTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("cycle.xml"); A a = ac.getBean(A.class);
System.out.println(a.getB()); B b = ac.getBean(B.class);
System.out.println(b.getA());
}
}
6 Debug运行
下图是A、B两个对象的创建和属性赋值主要的流程图,配合下面的步骤来看
7 进入refresh方法
从ClassPathXmlApplicationContext的构造方法进入refresh方法
8 finishBeanFactoryInitialization(beanFactory)方法
refresh方法中共分为了十三步来处理,我们跳过前面的,直接进入finishBeanFactoryInitialization()方法,这个方法真正的开始进行实例化操作了。
9 preInstantiateSingletons方法
跳过finishBeanFactoryInitialization方法中一系列的设置,一直到beanFactory.preInstantiateSingletons()方法
这个方法就是开始实例化剩余的所有的非懒加载的对象,进去这个方法,从preInstantiateSingletons这个方法开始,正儿八经的开始实例化了
我们共有两个对象需要实例化,我们看beanNames集合,里面有两个元素a和b, 接下来,会循环beanNames这个集合来创建对象
10 我们进入循环,查看对象的创建的主要过程,按照下图的方法来看
11 进入循环后,一步步往下走,会看到getBean方法
12 进入getBean方法,会看到doGetBean方法
13 getSingleton-从缓存中获取实例
进入dogetbean方法,我们会看到它调用了getSingleton方法,这个方法就是会缓存中获取
14 进入getSingleton方法
进入getSingleton
再进入getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
//从单例对象缓存(一级缓存)中获取缓存的单例对象
Object singletonObject = this.singletonObjects.get(beanName);
//如果没有,并且该beanName对应的单例bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//从早期单例对象缓存(二级缓存)中获取早期单例对象(之所以称为早期单例对象,是因为里面的对象都是通过提前曝光的ObjectFactory创建出来的,还没有进行属性填充等操作)
singletonObject = this.earlySingletonObjects.get(beanName);
//如果早期单例对象缓存(二级缓存)中也没有,并且允许创建早期单例对象应用
if (singletonObject == null && allowEarlyReference) {
//锁定全局变量并进行处理
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//当某些方法需要提前初始化的时候会调用addSingletonFactory方法将对应的ObjectFactory初始化策略存储在singletonFactory(三级缓存)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//如果存在单例对象工厂,则通过工厂创建一个单例对象
singletonObject = singletonFactory.getObject();
//存储在二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
此时先去以及缓存中查找a对象,不存在
进入if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) 判断,这个判断是a对象不为空,并且a对象正在创建中时为true。由于现在只是在查找是否存在a对象,还没有开始创建a对象,所以判定为false,不会进去。直接返回一个null
15 再次回到getBean方法
Object sharedInstance = getSingleton(beanName);获取的是null
16 继续往下走
再次看到了getSingleton方法,这里传入的是一个函数式接口(lamada表达式)
17 进入getSingleton方法
我们看到这个方法有两个参数:beanName和ObjectFactory。所以上面传入的函数式接口(lamada)就是ObjectFactory的实现
我们查看ObjectFactory,发现它就只有一个方法getObject
@FunctionalInterface
public interface ObjectFactory<T> { /**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException; }
接种往下走,我们会看到以下调用,它实际上调用的就是刚才传入的函数式接口(lamada表达式),里面调用了createBean方法
singletonObject = singletonFactory.getObject();
18 进入singletonFactory.getObject(),开始执行createBean方法
再进入createBean方法
19 进入createbean方法
跳过前面的一系列逻辑判断,到达doCreateBean方法
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
20 进入doCreateBean方法,到达createBeanInstance方法
跳过前面的逻辑判断,到达createBeanInstance方法,这个方法会创建对象
instanceWrapper = createBeanInstance(beanName, mbd, args);
执行完这个方法,我们会看到A对象已经创建了A@1892
且此时a对象里面的b属性是空的,所以此时a对象是个半成品
21 addSingletonFactory
接着往下走,到达addSingletonFactory方法,这个方法传入两个参数,beanName和一个函数式接口(lamada表达式)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
进入addSingletonFactory方法,这个方法两个参数beanName和ObjectFactory,所以刚才传入的函数式接口(lamada)是ObjectFactory的实现
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
//使用singletonObjects加锁,保证线程安全
synchronized (this.singletonObjects) {
//如果一级缓存中没有beanName对象
if (!this.singletonObjects.containsKey(beanName)) {
//将beanName:ObjectFactory放入三级缓存中
this.singletonFactories.put(beanName, singletonFactory);
//移除二级缓存中的beanName对象
this.earlySingletonObjects.remove(beanName);
//将beanName添加到已注册的单例集中
this.registeredSingletons.add(beanName);
}
}
}
往下执行,现在以及缓存中没有a对象,将beanName:ObjectFactory(也就是那个函数式接口)放入三级缓存中,这里放入三级缓存的是ObjectFactory而不是a对象
22 执行完addSingletonFactory回到doCreateBean继续执行,来到populateBean方法
//对bean的属性进行填充,将各个属性注入,其中,可能存在依赖其他bean的属性,那么会递归创建依赖的bean
populateBean(beanName, mbd, instanceWrapper);
23 进入populate方法,找到applyPropertyValues方法
跳过前面的一系列处理,来到最后一行applyPropertyValues方法,这个方法对属性进行填充
applyPropertyValues(beanName, mbd, bw, pvs);
进入applyPropertyValues方法,往下来之来到一下代码处
//获取属性名称
String propertyName = pv.getName();
//获取未经类型转换的值
Object originalValue = pv.getValue();
if (originalValue == AutowiredPropertyMarker.INSTANCE) {
//获取preopertyName的setter方法
Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod();
我们看到这里获取的originalValue是一个RuntimebeanReference对象
24 接着往下走来到resolveValueIfNecessary方法
//交由valueResolver根据pv解析出originalValue(RuntimebeanReference)所封装的对象
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
进入resolveValueIfNecessary方法,这个判断为true,调用resolveReference方法,解析封装的对象
if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
//解析出ref(RuntimeBeanReference)封装的bean元信息的bean对象
return resolveReference(argName, ref);
}
25 进入resolveReference方法
进入resolveReference方法往下走,来到下面代码
//resolvedName=ref保证的bean的名称
resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
//获取resolvedName的bean对象
bean = this.beanFactory.getBean(resolvedName);
这里又回到了getBean方法,不过这里查找的是b对象,为了给a对象的b属性赋值
26 又回到了getBean方法,这里针对的额是b对象
和a对象一样的步骤,12到20步走下来,创建了b对象b@1736
27 创建了b对象后,需要对b进行属性填充
和a的步骤一样,接着20步走到25步,又回到了getBean方法,此时去获取的是a对象(为了给b对象的a属性赋值)
28 又回到getBean方法,走到getSingleton
和 12 13步一样,来到getSingleton-从缓存中获取实例方法,从缓存中去获取a对象
进入getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
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;
}
从一级缓存获取a对象不存在
进入下面的判断,此时a正在创建中,所以判断为true,进入
从二级缓存中获取a对象,还是没有
通过singletonObjects加锁
再次判断一级缓存和二级缓存是否存在a对象,还是没有
从三级缓存中获取singletonFactory对象。前面我们已经在三级缓存中存入了两个singletonFactory对象(函数式编程lamada表达式)
接着调用singletonFactory.getObject()
进入getObject,实际上调用的是我们传入的lamada表达式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;
}
执行完成得到a对象
再把a对象(还是个半成品,b属性没有值)放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
再从三级缓存中移除a的singletonFactory对象
this.singletonFactories.remove(beanName);
29 接着往下走,回到applyPropertyValues方法
继续往下执行,到达下面代码,给b对象的a属性赋值
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
30 继续执行
继续往下执行,执行完applyPropertyValues,执行完populateBean(beanName, mbd, instanceWrapper)回到了doCreateBean方法中
此时,看b对象中的a属性已有值了,但是a里面b属性还没有值
31 继续往下执行,一直回到getSingleton方法
在该方法中继续往下执行,来到下面代码,addSingleton方法
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
进入addSingleton方法
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//一级缓存中存入对象
this.singletonObjects.put(beanName, singletonObject);
//三级缓存中移除对象
this.singletonFactories.remove(beanName);
//二级缓存中移除对象
this.earlySingletonObjects.remove(beanName);
//将beanName添加到已注册单例集中
this.registeredSingletons.add(beanName);
}
}
将完整的b对象(a属性有值)存入一级缓存,再将二级、三级缓存中的b对象移除
32 继续往下走
刚才创建b对象时为了给a对象的b属性赋值,现在b对象有了,该给a对象的b属性赋值了
继续往下走,回到applyPropertyValues方法
再次执行bw.setPropertyValues(new MutablePropertyValues(deepCopy));
此时给a对象里面的b属性赋值
33 继续执行
执行完applyPropertyValues,一直回到doCreateBean方法,populateBean方法执行完成
此时a对象的b属性有值了
34 重复第31部
继续往下执行,一直回到getSingleton方法
在该方法中继续往下执行,来到下面代码,addSingleton方法
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
进入addSingleton方法
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//一级缓存中存入对象
this.singletonObjects.put(beanName, singletonObject);
//三级缓存中移除对象
this.singletonFactories.remove(beanName);
//二级缓存中移除对象
this.earlySingletonObjects.remove(beanName);
//将beanName添加到已注册单例集中
this.registeredSingletons.add(beanName);
}
}
将完整的a对象(b属性有值),再将二级、三级缓存中的a对象移除
此时,我们a、b对象,a中有b,b中有a
35 继续往下走,回到preInstantiateSingletons方法
此时a、b对象都已创建
所以,在下面这个循环中,不会再去创建b对象,而是直接获取到b对象
到此,a、b创建完成,且成功解套
Spring03-IOC-循环依赖的实现(Debug查看spring解决循环依赖的过程)的更多相关文章
- Spring解决循环依赖
1.Spring解决循环依赖 什么是循环依赖:比如A引用B,B引用C,C引用A,它们最终形成一个依赖环. 循环依赖有两种 1.构造器循环依赖 构造器注入导致的循环依赖,Spring是无法解决的,只能抛 ...
- 曹工说Spring Boot源码(29)-- Spring 解决循环依赖为什么使用三级缓存,而不是二级缓存
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 浅谈Spring解决循环依赖的三种方式
引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...
- Spring解决循环依赖,你真的懂了吗?
导读 前几天发表的文章SpringBoot多数据源动态切换和SpringBoot整合多数据源的巨坑中,提到了一个坑就是动态数据源添加@Primary接口就会造成循环依赖异常,如下图: 这个就是典型的构 ...
- 建议收藏!利用Spring解决循环依赖,深入源码给你讲明白!
前置知识 只有单例模式下的bean会通过三级缓存提前暴露来解决循环依赖的问题.而非单例的bean每次获取都会重新创建,并不会放入三级缓存,所以多实例的bean循环依赖问题不能解决. 首先需要明白处于各 ...
- 使用vs工具查看dll依赖(也可查看pyc文件的依赖)
vs工具中有个工具叫dumpin.exe,可以用来查看exe文件.dll文件.pyc文件依赖于哪些dll,从而针对性地去检查具体缺失哪些文件(目前是在装TensorFlow时查看具体需要哪个版本的cu ...
- Spring当中循环依赖很少有人讲,今天一起来学习!
网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...
- Spring 是如何解决循环依赖的?
前言 相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的: 还会提示这么一句: Requested bean is currently in creation: Is there an ...
- Spring是如何解决循环依赖的
前言 在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的.这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过s ...
- Spring的循环依赖
本文简要介绍了循环依赖以及Spring解决循环依赖的过程 一.定义 循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环.这里的对象特指单例对象. 二.表现形式 对象之间的循环 ...
随机推荐
- 一、Redis的Java客户端
模糊的目标,要不断去解释它们,把他们转化成一个更具体的内容,这样才能够找到途径. 常用客户端介绍 Jedis客户端 基本使用(直连) 引入对应依赖 <dependency> <gro ...
- 2022春每日一题:Day 15
题目:Balanced lineup 题目说的很清楚了,没有修改,直接RMQ,模板题. 代码: #include <cstdio> #include <cstdlib> #in ...
- Go语言核心36讲27
在前面的文章中,我们一起学习了Go程序测试的基础知识和基本测试手法.这主要包括了Go程序测试的基本规则和主要流程.testing.T类型和testing.B类型的常用方法.go test命令的基本使用 ...
- UBOOT编译--- include/config.h、 include/autoconf.mk、include/autoconf.mk.dep、u-boot.cfg(三)
1. 前言 UBOOT版本:uboot2018.03,开发板myimx8mmek240. 2. 概述 本节主要接上一节解析 :include/config.h. include/autoconf.mk ...
- VS使用web deploy发布到远程服务器
如果是先安装 web deploy后安装iis的功能,需要在iis功能安装好后,修复下web deploy(直接运行web deploy的安装程序有修复)(本人也死在这里) 1.iis开启管理服务,和 ...
- HCIE Routing&Switching之MPLS基础理论
技术背景 90年代初期,互联网流量快速增长,而由于当时硬件技术的限制,路由器采用最长匹配算法逐跳转发数据包,成为网络数据转发的瓶颈:于是快速路由技术成为当时研究的一个热点:在各种方案中,IETF确定了 ...
- SpringCloud Alibaba(六) - Seata 分布式事务锁
1.Seata 简介 1.1 Seata是什么 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事 ...
- VSCODE 中.art文件识别为html文件
setting.json文件中 { "git.ignoreMissingGitWarning": true, "explorer.confirmDelete": ...
- TCP\UDP协议 socket模块
目录 传输层主要协议 TCP协议 三次握手 TCP协议反馈机制 四次挥手 洪水攻击 UDP协议 socket模块 socket代码简介 socket.socket() server.bind() se ...
- jmeter 之性能分布式压测
背景: 当并发量达到一定数量时,单台测试设备不足以支撑,甚至会出现内存溢出等情况,解决这个问题就可用分布式测试,使用多台测试设备来达到更大的用户并发数. 原理: 1.一台设备作为调度机(master) ...