从Spring源码看Spring如何解决循环引用的问题
Spring如何解决循环引用的问题
关于循环引用,首先说一个结论:
Spring能够解决的情况为:两个对象都是单实例、且通过set方法进行注入。
两个对象都是单实例,通过构造方法进行注入,Spring不能进行循环引用问题;
两个对象都是多实例的情况下,不管是set注入,还是构造注入,都不能解决Spring循环引用问题。
循环引用问题介绍
循环引用问题即:
有A,B两个类,A类中有B类型的成员变量b、B类中有A类型的成员变量a。创建a的过程需要b,创建b的过程又需要a;

循环引用问题分析
请看如下流程:
- 调用getBean("a")来获取a对象;
- 先调用getSingleton("a")来尝试获取a,但是获取不到;
- 需要调用doCreateBean()来创建a;
- a的b属性是null,需要填充b属性;
- 调用getBean("b")来获取b对象;
- 先调用getSingleton("b")来尝试获取b,但是获取不到;
- 需要调用doCreateBean()来创建b;
- b的a属性是null,需要填充a属性;
- 又需要要调用getBean("a")来获取a。
这时getBean("a")可以获取到吗?如果能获取到,是在哪里获取的?如果获取不到,又会有什么问题呢?
我们首先看下getSingleton()源码:

addSingleton方法如下图:

如此可以看到,在进行实例化、属性填充、初始化都完成后才会放到singletonObjects中。
那getSingleton()方法就获取不到a,只能再去创建a对象了吗?当然不是,如果再去创建a,a就不是单例的呢。
所以这就需要没有创建完全的a也要存储起来。但是并没有存储到singletonObjects中,因为singletonObjects是存储例化、属性填充、初始化都完成后的对象。
Spring又为我们定义了两个存储的位置:earlySingletonObjects、singletonFactories。
那什么时候将未创建完全的对象存储起来呢?
这我们应该在实例化对象完成后,填充属性前的代码查找。可以看到如下代码:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
addSingletonFactory方法源码如下:
protected void addSingletonFactory(String beanName, ObjectFactory <? > singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized(this.singletonObjects) {
if(!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
实例化后,会把创建非完全体对象的工厂放到singletonFactories里,这个工厂就是lambda表达式() -> getEarlyBeanReference(beanName, mbd, bean)调用的getEarlyBeanReference(beanName, mbd, bean)方法。

addSingletonFactory还会把earlySingletonObjects、registeredSingletons中的对象删除。
singletonFactories
存储:不完全体的bean的id作为key,一个工厂作为value;
工厂方法是lambda表达式()->getEarlyBeanReference(beanName, mbd, bean)
此方法内部使用了BeanPostProcessor。
singletonFactories为什么不存储未完全体的a,而存储一个工厂方法呢?
这意味着他会处理一些复杂功能。

上述介绍的循环引用的问题,是最简单的情况。还有一些复杂情况。
如果A需要做AOP,需要为A做代理呢?或者B也要做代理呢?

代理是在初始化阶段使用BeanPostProcessor的postProcessAfterInitialization()方法来做的。

singletonFactories存工厂的原因:
为b填充属性a时,需要获取到不完全体的a,为b赋值;
并且如果A需要做代理;
而代理是在BeanPostProcessor中的postProcessAfterInitialization()方法做的;
所以singletonFactories存储的是一个工厂(里面的方法是用BeanPostProcessor中的);
这样就无需在a初始化的过程中创建代理了,可以把a的代理提前创建出来。
那在A创建过程中是否还要创建代理呢?————不会。
在上面提前创建a的代理完成后,会将代理对象放到代理缓存中,在a初始化创建代理时,直接从代理缓存中拿就可以了。
站在b的角度讲,现在b的属性填充完成了,后面就是初始化了,在初始化过程中,就可以走正常的代理过程了。
a在填充属性时,就可以填充b的代理了,就可以走初始化了,初始化过程中的代理从代理缓存获取就可以了。
为b填充a代理对象分析
doGetBean()中的getSingleton方法:



在为b填充a的代理时,singletonFactory.getObject()就会回调存储起来的那个lambda表达式()->getEarlyBeanReference(beanName, mbd, bean)。

会把a的代理获取出来;
然后把a的代理放到earlySingletonObjects中;
把存储的a工厂的lambda表达式从singletonFactories中移除。
b初始化完成后,b就是完全体了,调用addSingleton()方法就会把b存储到singletonObjects中了。
等a再初始化完成就是完全体了。
这样就解决了循环引用问题。
从Spring源码看Spring如何解决循环引用的问题的更多相关文章
- spring源码系列(一)sring源码编译 spring源码下载 spring源码阅读
想对spring框架进行深入的学习一下,看看源代码,提升和沉淀下自己,工欲善其事必先利其器,还是先搭建环境吧. 环境搭建 sping源码之前是svn管理,现在已经迁移到了github中了,新版本基于g ...
- Spring源码解析(五)循环依赖问题
引言 循环依赖就是多个类之间互相依赖,比如A依赖B,B也依赖A,如果日常开发中我们用new的方式创建对象,这种循环依赖就会导致不断的在创建对象,导致内存溢出. Spring是怎么解决循环依赖的问题的? ...
- spring源码解析——spring源码导入eclipse
一.前言 众所周知,spring的强大之处.几乎所有的企业级开发中,都使用了spring了.在日常的开发中,我们是否只知道spring的配置,以及简单的使用场景.对其实现的代码没有进行深入的了 ...
- Spring源码阅读-spring启动
web.xml web.xml中的spring容器配置 <listener> <listener-class>org.springframework.web.context.C ...
- Spring源码分析(十七)循环依赖
本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 实例化bean是一个非常复杂的过程,而其中比较难以理解的就是对循环依赖的解决, ...
- Spring源码:Spring IoC容器加载过程(2)
Spring源码版本:4.3.23.RELEASE 一.加载XML配置 通过XML配置创建Spring,创建入口是使用org.springframework.context.support.Class ...
- 初探Spring源码之Spring Bean的生命周期
写在前面的话: 学无止境,写博客纯粹是一种乐趣而已,把自己理解的东西分享出去,不意味全是对的,欢迎指正! Spring 容器初始化过程做了什么? AnnotationConfigApplication ...
- Spring源码:Spring IoC容器加载过程(1)
Spring源码版本:4.3.23.RELEASE 一.加载过程概览 Spring容器加载过程可以在org.springframework.context.support.AbstractApplic ...
- 【Spring 源码】Spring 加载资源并装配对象的过程(XmlBeanDefinitionReader)
Spring 加载资源并装配对象过程 在Spring中对XML配置文件的解析从3.1版本开始不再推荐使用XmlBeanFactory而是使用XmlBeanDefinitionReader. Class ...
- 从源码看Spring Security之采坑笔记(Spring Boot篇)
一:唠嗑 鼓捣了两天的Spring Security,踩了不少坑.如果你在学Spring Security,恰好又是使用的Spring Boot,那么给我点个赞吧!这篇博客将会让你了解Spring S ...
随机推荐
- 莉莉丝游戏与火山引擎 ByteHouse 达成合作,为实时数仓建设提速
中国头部游戏公司莉莉丝游戏(Lilith Games)和火山引擎 ByteHouse 达成合作,共同致力于加速莉莉丝游戏的实时数仓建设.此次合作将利用 ByteHouse 的创新技术和功能,为莉莉丝的 ...
- PlayWright安装及使用
PlayWright是由业界大佬微软(Microsoft)开源的端到端 Web 测试和自动化库,可谓是大厂背书,功能满格,虽然作为无头浏览器,该框架的主要作用是测试 Web 应用,但事实上,无头浏览器 ...
- Bug定级实例
*1级,**系统崩溃* *定义:*严重阻碍测试和开发工作 *对应**优先级**:**最高* *具体可分为:* 1.功能完全没有实现 2.应用闪退/崩溃无法运行 3*.应用必现安全模式,无法运行* 4. ...
- AtCoder Beginner Contest 210 (A~E)
比赛链接:Here A - Cabbages 略 B - Bouzu Mekuri 略 C - Colorful Candies 用map维护连续一段区间的不同元素即可. int main() { c ...
- 彻底搞懂 IO 底层原理
武侠小说里有很多的"心法"和"招式".计算机技术里的"心法"和"招式"呢,我们可以简称为"道"和&q ...
- P2895(未解决)
这是一道略复杂的常规BFS题,但我想用DFS来解决,结果写出代码却总是主函数异常返回,不知哪里错了,检查半天也没发现,以后再看看吧. Code #include<iostream> #in ...
- C#设计模式18——迭代器模式的写法
是什么: 迭代器模式是一种行为型设计模式,它允许客户端通过一种统一的方式遍历集合对象中的元素,而无需暴露集合对象的内部结构. 为什么: 使用迭代器模式可以使得客户端程序与集合对象解耦,从而可以更加灵活 ...
- 扒一扒爱奇艺影视剧新功能——AI识别、GIF动画生成、画面截图涂鸦
AI 识别 点击画面中的 右侧出现 查看详情 GIF动画生成 点击画面中的 生成GIF动画 画面截图涂鸦 截图 涂鸦
- MongoDB 根据多个条件批量修改
转载请注明出处: MongoDB 根据单个条件修改的sql 如下: db.collection_name.update({"userid":"1111111"} ...
- python毕业设计选题15例,马上要毕业啦,大家做好准备了没
Hi,大家好,大四的同学马上要开始毕业设计啦,大家做好准备了没! 学长给大家详细整理了最新的python计算机毕设相关选题,对选题有任何疑问,都可以问学长哦. 1. 网上商城系统 这是一个基于pyth ...