Spring如何解决循环引用的问题

关于循环引用,首先说一个结论:

Spring能够解决的情况为:两个对象都是单实例、且通过set方法进行注入

两个对象都是单实例,通过构造方法进行注入,Spring不能进行循环引用问题;

两个对象都是多实例的情况下,不管是set注入,还是构造注入,都不能解决Spring循环引用问题。

循环引用问题介绍

循环引用问题即:

有A,B两个类,A类中有B类型的成员变量b、B类中有A类型的成员变量a。创建a的过程需要b,创建b的过程又需要a;

循环引用问题分析

请看如下流程:

  1. 调用getBean("a")来获取a对象;
  2. 先调用getSingleton("a")来尝试获取a,但是获取不到;
  3. 需要调用doCreateBean()来创建a;
  4. a的b属性是null,需要填充b属性;
  5. 调用getBean("b")来获取b对象;
  6. 先调用getSingleton("b")来尝试获取b,但是获取不到;
  7. 需要调用doCreateBean()来创建b;
  8. b的a属性是null,需要填充a属性;
  9. 又需要要调用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如何解决循环引用的问题的更多相关文章

  1. spring源码系列(一)sring源码编译 spring源码下载 spring源码阅读

    想对spring框架进行深入的学习一下,看看源代码,提升和沉淀下自己,工欲善其事必先利其器,还是先搭建环境吧. 环境搭建 sping源码之前是svn管理,现在已经迁移到了github中了,新版本基于g ...

  2. Spring源码解析(五)循环依赖问题

    引言 循环依赖就是多个类之间互相依赖,比如A依赖B,B也依赖A,如果日常开发中我们用new的方式创建对象,这种循环依赖就会导致不断的在创建对象,导致内存溢出. Spring是怎么解决循环依赖的问题的? ...

  3. spring源码解析——spring源码导入eclipse

    一.前言     众所周知,spring的强大之处.几乎所有的企业级开发中,都使用了spring了.在日常的开发中,我们是否只知道spring的配置,以及简单的使用场景.对其实现的代码没有进行深入的了 ...

  4. Spring源码阅读-spring启动

    web.xml web.xml中的spring容器配置 <listener> <listener-class>org.springframework.web.context.C ...

  5. Spring源码分析(十七)循环依赖

    本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 实例化bean是一个非常复杂的过程,而其中比较难以理解的就是对循环依赖的解决, ...

  6. Spring源码:Spring IoC容器加载过程(2)

    Spring源码版本:4.3.23.RELEASE 一.加载XML配置 通过XML配置创建Spring,创建入口是使用org.springframework.context.support.Class ...

  7. 初探Spring源码之Spring Bean的生命周期

    写在前面的话: 学无止境,写博客纯粹是一种乐趣而已,把自己理解的东西分享出去,不意味全是对的,欢迎指正! Spring 容器初始化过程做了什么? AnnotationConfigApplication ...

  8. Spring源码:Spring IoC容器加载过程(1)

    Spring源码版本:4.3.23.RELEASE 一.加载过程概览 Spring容器加载过程可以在org.springframework.context.support.AbstractApplic ...

  9. 【Spring 源码】Spring 加载资源并装配对象的过程(XmlBeanDefinitionReader)

    Spring 加载资源并装配对象过程 在Spring中对XML配置文件的解析从3.1版本开始不再推荐使用XmlBeanFactory而是使用XmlBeanDefinitionReader. Class ...

  10. 从源码看Spring Security之采坑笔记(Spring Boot篇)

    一:唠嗑 鼓捣了两天的Spring Security,踩了不少坑.如果你在学Spring Security,恰好又是使用的Spring Boot,那么给我点个赞吧!这篇博客将会让你了解Spring S ...

随机推荐

  1. 信创就用国产的生态,Solon v2.6.4 发布

    Solon 是什么框架? Java 新的"生态级"应用开发框架.从零开始构建,有自己的标准规范与开放生态(历时六年,具备全球第二级别的生态规模). 相对于 Spring,有什么特点 ...

  2. 网页“悼念模式”全站变灰/黑白色CSS代码

    <style> html { filter:grayscale(100%); -webkit-filter:grayscale(100%); -moz-filter:grayscale(1 ...

  3. 微服务系列-如何使用 RestTemplate 进行 Spring Boot 微服务通信示例

    概述 下面我们将学习如何创建多个 Spring boot 微服务以及如何使用 RestTemplate 类在多个微服务之间进行同步通信. 微服务通信有两种风格: 同步通讯 异步通信 同步通讯 在同步通 ...

  4. 透析阿里云视频云「低代码音视频工厂」之能量引擎——vPaaS 视频原生应用开发平台

    为满足企业用户极速搭建高品质专属音视频业务的需求,阿里云视频云的"低代码音视频工厂"应运而生,但极速而高品质的平台搭建诉求,需要用全新的开发方式才能真正实现,而全新的开发方式背后蕴 ...

  5. Windows 环境下安装与配置 Node.js

    一.下载Node.js安装包 下载地址:http://nodejs.cn/download/ 本教程以msi安装包为例 点击Windows 安装包下载 二.安装Node.js 1.打开安装包 欢迎页: ...

  6. LeetCode | 983.最低票价(动态规划)

    在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行.在接下来的一年里,你要旅行的日子将以一个名为days的数组给出.每一项是一个从 1 到 365 的整数. 火车票有三种不同的销售方式: 一张 ...

  7. VSCode一键调用DOSBox运行MASM/TASM代码的自定义任务

    在学习汇编的时候,发现很多教程都未使用 VSC 进行开发,今天在 Gitee 看有意思的项目时候发现了这个 DOS汇编/VSC-ASMtasks 通过配置VSCode的自定义任务来实现调用dosbox ...

  8. 人人都是 Serverless 架构师 | “盲盒抽奖”创意营销活动实践

    作者 | 寒斜 & 江昱 当 Serverless 与低代码这两个不同的技术共同相交于同一个业务时会有怎样的价值展现?本文以 "盲盒抽奖" 这个 Serverless De ...

  9. oracle开机自动重启

    数据库服务器如果由于某种原因重启了,oracle数据库是不会重新启动的,那么如何配置可以完成操作系统重启数据库服务器自动重启. 注:如下样例根据我的实际oracle安装路径写的,使用时根据实际安装路径 ...

  10. win32com操作word API精讲 第八集 Range和Selection的区别

    本课程<win32com操作word API精讲&项目实战>以视频为主,文字为辅,公众号ID:一灯编程 众所周知,在word编程中Range和Selection都能实现范围的选中, ...