从spring源码层面分析循环依赖解决方案的实现原理(下)

简介: 从spring源码层面分析循环依赖解决方案的实现原理(下)

三级缓存中包含A和B的2个lambda表达式,A和B对象还没有放入缓存中。

从容器中查询a

image.png

一级缓存和二级缓存都没有,三级缓存中虽然没有a对象,但是有ObjectFactory。执行 singletonFactory.getObject()实际上调用的是lambda表达式getEarlyBeanReference(beanName, mbd, bean)。


image.png

如果有代理对象,则返回代理对象,如果没有代理对象,则返回原始对象。

因为xml中并没有给a对象配置aop代理,所以这里返回的是a的原始对象。

image.png

查看getEarlyBeanReference方法可以看到AOP代理的两种实现方式。


image.png

image.png

image.png

为什么删除三级缓存a的objectFactory,因为查找顺序是先查一级缓存,再查二级缓存,最后查三级缓存,二级缓存已经有了a对象,所以三级缓存内容就没有留着的必要了,这也是三级缓存容量比较小的原因。

获取a对象的目的是给b对象中的a属性赋值。


image.png

image.png

image.png

image.png

那么就不需要createBean了,整个逻辑才算结束了。

总结

1、三个map结构中分别存储了什么对象?

一级缓存:成品对象;二级缓存:半成品对象;三级缓存:lambda表达式(getEarlyReference)

2、三个map缓存的查找对象的顺序?

先找一级缓存,查不到,再查二级缓存,再找不到,找三级缓存。

3、如果只有一个map,能否解决循环依赖问题?

不能,如果只有一个map,那么成品对象和半成品对象只能放入一个map中,而半成品对象是不能暴露给外部使用的,所以必须要做区分,否则就有可能暴露半成品对象。

有人可能说添加一个标识,0=半成品、1=成品,每次取的时候都要判断下这个标识别,很麻烦。

4、如果只有两个map,能否解决循环依赖问题?

能,但有前提条件。

修改源码1

把createBean方法中的

this.addSingletonFactory(beanName, () -> {
                return this.getEarlyBeanReference(beanName, mbd, bean);
            });

注释掉,然后添加,earlyearlySingletonObjects.put(beanName,bean)

即不往三级缓存中放入ObjectFactory对应的lambda表达式,而是往二级haunch中放入bean对象。

修改源码2

把getSingleton方法中的

image.png

这一部分,换成

singletonObject=this.earlySingletonObjects.get(beanName);
return singletonObject;

即不从三级缓存中获取,而是直接从二级缓存获取。

运行下发现并没有报错


image.png

再来运行代码,就会报错:other beans do not use the final version of the bean即其他bean不能使用最终版本的bean。

所以只要不包含aop就可以使用二级缓存解决循环依赖问题,但是出现aop之后,就必须要使用三级缓存了。

5、为什么三级缓存就可以解决循环依赖中包含的代理对象问题呢?

(1)创建代理对象的时候是否需要创建出原始对象?

在没有生成代理对象之前就可以生成原始对象了

image.png

image.png

这个方法是创建代理对象的。

(2)同一个容器中能否出现两个不同的对象?不能,对象名都叫a,要么是原始对象要么是代理对象,不能同时出现。

(3)如果一个对象被代理,那么原始对象和和代理对象应该这么去存储?

如果需要代理对象,那么代理对象创建完之后应该覆盖原始对象。

在getEarlyBeanReference方法中,会判断是否需要代理对象,如果创建出了代理对象,就需要覆盖原始对象。

(4)在对象对外暴露的时候,容器怎么知道什么时候需要被暴露呢?或者说在对象对外暴露的时候,如何准确的给出原始对象或代理对象?

因为正常的代理对象的创建是在BeanPostProcessor的后置方法中,在解决循环依赖问题的时候还没有执行到那个地方,所以此时就需要lambda表达式了,类似于一种回调机制,在确定要对外暴露的时候,就唯一性确定到底是代理对象还是原始对象,这也是什么不把对象直接放入二级缓存中,而通过三级缓存lambda表达式的方式来执行的原因。


image.png

addSingletonFactory这里把lambda表达式放入三级缓存,该表达式的内容是判断是否需要代理对象,若需要则创建,这里并没有真正的去执行该表达式。

initializeBean这个方法中的applyBeanPostProcessorsBeforeInitialization方法才是真正的通过后置方法beanPostProcessor创建代理对象,而在处理循环依赖的时候并没有执行到这一步呢。

程序没有办法去确定你写的这个对象什么时候被其他对象调用,什么时候需要变成某一个对象的属性,所以把他换成一个lambda表达式,在确定需要对外暴露的时候才执行对象的确定(原始还是代理)。

先创建a对象,再创建b对象,需要给b中的a属性赋值,从三级缓存中找到lambda表达式,然后判断是否需要代理对象。lambda表达式相当于回调机制,并不会立刻执行,当你需要给这个属性赋值的时候,你才会去执行。

image.png

刚开始是原始对象,如果需要被代理,则返回被代理之后的对象。

某一个对象的代理只能是一个,如果是多个代理的话,就需要看代理对象的创建顺序了。

(5)spring提供了循环依赖的解决方案,那日常工作中是否也会有遇到循环依赖的问题?

spring是一个跟业务无关的技术框架,它只能预防一些问题,而不是解决所有问题,就跟我们写代码的时候的异常处理一样,你能判断到一些问题,但不是所有的异常情况你都能全部解决掉的。


image.png

相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
1月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
36 0
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
1月前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
23天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
44 2
|
1月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
67 9
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
245 2
|
3天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
50 1
SpringBoot入门(7)- 配置热部署devtools工具