Spring 循环依赖解析(下)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Spring 现在其实是我们 Java 程序开发离不开的基础框架,个人觉得除了 JDK 我们用得最多的 Java 中间件就是 Spring ,今天我们一起来学习一下 Spring 的循环依赖。

Spring 三级缓存介绍和循环依赖解决过程

三级缓存介绍


核心类: DefaultSingletonBeanRegistry


image.png


第一级缓存:(也叫单例池)singletonObjects: 存放已经经历完整生命周期的 Bean 对象


第二级缓存:earlySingletonObjects: 存放早起暴露出来的 Bean 对象, Bean 的生命周期未结束(属性还未填充完成)


第三级缓存:Map<String, ObjectFactory<?>> singletonFactories可以存放 Bean 工厂。


只有单例 Bean 会通过三级缓存提前暴露出来解决循环依赖问题,而非单例的 bean, 每次从容器获取都是新的对象,都会重新创建,所以非单例的 bean 是没有缓存的,不会放到三级缓存中。


实例化/初始化定义


实例化/初始化


1、实例化:内存中申请一块内存空间;(比如:租赁好好房子,自己的家具沙发,床还没有搬进去。)


2、初始化属性填充:完成各种属性的赋值;(比如:装修,家电家具进场)


三级缓存使用过程


3 个 map 的四大方法,总体相关对象


image.png


第一层:(也叫单例池)singletonObjects: 存放已经初始化好的 Bean。


第二层:earlySingletonObjects: 存放的是实例化的了, 但是未初始化的 Bean。


第三层:Map<String, ObjectFactory<?>> singletonFactories存放的是 FactroyBean。假如 A 类实现了 FactoryBean, 那么依赖注入的时候不是 A 类,而是 A类的 FAC天FactoryBean。


/**
 * 单例对象的缓存:bean 名称--Bean 实例, 即:所有的单例池
 * 表示经历了完整生命周期的 Bean 对象
 * <b>第一级缓存</b>
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
 * 单例工厂的高速缓存:bean 名称--ObjectFacotry
 * 表示存放生成的 bean 工厂
 * <b>第三级缓存</b>
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
 * 早起的单例对象的高速缓存:bean 名称--Bean 实例
 * 表示 Bean 的生命周期还没有走完(Bean 的属性还未填充)就把这个 Bean 存入该缓存中
 * 也就是实例化的 bean 放入了该缓存中
 * <b>第二级缓存</b>
 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);


A/B 两对象在三级缓冲的迁移说明


1、创建 A 过程中需要 B, 于是 A 将自己放入到三级缓存里面,去实例化 B


2、B 实例化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有再查三级缓存,找到了 A 然后把三级缓存里面的这个 A 放入到二级缓存里面,并且删除三级缓存里面的 A。


3、 B 顺利初始化完毕后,将自己放入到一级缓存里面(此时 B 里面的 A 依然是创建中状态)后来接着创建 A, 此时 B 已经创建结束,直接从一级缓存里面拿到 B,然后完成创建,并且将 A 自己放到一级缓存中


ObjectFactory 接口


@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;
}


DEBUG 断点调试


1、Bean 创建


image.png


2、放入三级缓存


image.png


3、属性填充(就是做属性注入)


image.png


循环依赖解决


整理流程梳理


1、调用 doGetBean() 方法 , 想要获取 beanA , 于是调用 getSingletion() 方法从缓存中查询 beanA


2、在 getSingletion() 方法中,从一级缓存中查找,没有,返回 null


3、doGetBean() 犯法中获取到 beanA 为 null, 于是就走对应的处理逻辑,调用 getSIngletion() 的重载方法(参数为 ObjecFactory  的)


4、在 getSingletion() 方法中,先将 beanB_name 添加到一个集合中,用于标记该 bean 正在创建中,然后回调匿名内部类的 createBean  方法


5、进入 AbstractAutowireCapableBenaFactory#doCreateBean , 先反射调用构造器创建出 beanA 的实例,然后判断,是否为单例、是否允许提前暴露引用(对于单例一般为 true)、是否正在创建中(即是在第四步的集合中)。判断为 true 则将 beanA 添加到【三级缓存】中。


6、对 beanA 进行属性填充,此时检测到 beanA 依赖 beanB , 于是开始查找 beanB


7、调用 doGetBean 方法,和上面的 beanA 过程一样,到缓存中查找 beanB, 没有则创建,然后给 beanB 填充属性。


8、此时 beanB  依赖于 beanA , 调用 getSingleton() 获取 beanA , 依次从一级、二级、三级缓存中找,此时三级缓存中获取到 beanA 的创建工厂,通过创建工厂获取到 singletonObject, 此时这个 singletonObject 指向的就是上面的 doCreateBean() 方法实例化的 beanA 。


9、这样 beanB 就获取到了 beanA 的依赖,于是 beanB 顺利完成实例话,并将 beanA 从三级缓存移动到二级缓存中。


10、随后 beanA  继续他的属性填充工作,此时也获取到了beanB , beanA 也随之完成了创建,回调 getSingleton() 方法中继续向下执行,将 beanA 从二级缓存中移动到一级缓存中。


Spring 循环依赖总结


Spring 创建 Bean 主要分为两个步骤,创建原始 Bean 对象,接着去填充属性和初始化。


每次创建 Bean 之前,我们都会从缓存中查一下有没有该 bean , 因为是单例, 只能有一个


当我们创建 beanA  的原始对象之后,并且把它放入到三级缓存中,接下来就该填充属性了,这个时候发现依赖了 beanB , 就直接去创建 beanB, 同样的流程,创建完 beanB 统筹国内属性时又发现了它依赖了 beanA  又是同样的流程。


不同的是


这个时候可以在三级缓存只能够查询到刚才放进去的原始对象 beanA , 所以不需要继续创建,用它注入 beanB , 完成 beanB 的创建


既然 beanB 创建好了,所以 beanA 就可以弯沉属性填充的步骤了,接下来执行剩下的逻辑完,完成闭环


@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 从 singletonObject 获取实例,singletonObject 中的实例都是准备好的 bean 实例
    Object singletonObject = this.singletonObjects.get(beanName);
    // isSingletonCurrentlyInCreation() 判断当前单例 bean 是否正在创建中
    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;
}


Spring 解决循环依赖靠的是 Bean 的“中间态” 这个概念。而这个中间态指的是 已经实例化还没有初始化的状态 ---> 半成品,实例话的过程有是通过构造器创建的,如果 A 还没有创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。


Spring 为了解决单例的循环依赖问题,使用了三级缓存。


其中一级缓存为单例池(singletonObjects)


二级缓存为提前曝光对象(earlySingletonObjects)


三级缓存为提前曝光对象工厂(singletonFactories)


假设 A,B 循环引用,实例化 A 的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了 B ,同样的流程也是实例化后放入三级缓存,接着去填充属性时发现自己依赖 A,这个时候从缓存中查找到早起暴露的 A,没有 AOP 代理的化,直接将 A 原始对象注入 B,完成 B的初始化后,进行属性填充和初始化,这个时候 B完成后,就去完成剩下 A的步骤,如果有 AOP 代理,就会进行 AOP 处理获取代理后的  A,注入 B, 走剩下的流程。


相关文章
|
19天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
57 2
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
161 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
68 2
|
2月前
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
73 2
|
2月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
53 4
|
2月前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
125 0
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
102 2
|
19天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
19天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多