Spring面试必问:手写Spring IoC 循环依赖底层源码剖析

简介: 在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。

概述

在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。

功能点

  1. 循环依赖的定义:两个或多个Bean相互依赖,形成闭环。
  2. Spring的解决方案:通过三级缓存机制解决循环依赖问题。
  3. 源码实现:手写核心代码,展示Spring如何解决循环依赖。

背景

在Spring中,获取一个单例Bean需要经过从缓存中查询、创建对象、依赖注入、将对象放入单例Bean缓存等步骤。当没有循环依赖时,这个流程运行得很好。但一旦出现循环依赖,就会出现问题。例如,Bean C在依赖注入Bean B时,缓存中没有Bean B(因为此时Bean B还没有完成创建),只能创建Bean B,从而导致重复创建(因为此时Bean B已经在创建中)。

业务点

Spring通过引入三级缓存机制来解决循环依赖问题:

  1. 一级缓存(singletonObjects):存储创建完毕的单例Bean。
  2. 二级缓存(earlySingletonObjects):存储已实例化但未完成创建的单例Bean。
  3. 三级缓存(singletonFactories):存储单例Bean对应的ObjectFactory,用于生成Bean实例。

底层原理

三级缓存的作用

  • singletonObjects:缓存完整的Bean对象。
  • earlySingletonObjects:缓存早期的Bean对象,即Bean的生命周期还未完全走完,但已经可以提前暴露给其他Bean使用。
  • singletonFactories:缓存ObjectFactory,用于在需要时创建Bean实例。

解决循环依赖的流程

  1. 创建Bean实例:通过反射调用构造方法创建Bean的原始对象。
  2. 判断是否存在循环依赖:如果Bean正在创建中,则存在循环依赖。
  3. 将Bean实例放入三级缓存:创建一个ObjectFactory并放入singletonFactories中,ObjectFactory的getObject方法会返回Bean的实例。
  4. 依赖注入:在依赖注入过程中,如果发现依赖的Bean不存在于一级缓存和二级缓存中,则从三级缓存中获取ObjectFactory并创建Bean实例。
  5. 将Bean实例放入二级缓存:如果Bean实例是通过ObjectFactory创建的,则将其放入earlySingletonObjects中。
  6. 完成Bean的生命周期:包括属性填充、AOP代理生成等步骤。
  7. 将Bean实例放入一级缓存:最终将完整的Bean对象放入singletonObjects中。

源码实现

下面是一个简化的源码实现,用于展示Spring如何解决循环依赖问题:

java复制代码
public class DefaultSingletonBeanRegistry {
// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 判断Bean是否正在创建中
private final Set<String> singletonsCurrentlyInCreation = new HashSet<>(16);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
                }
            }
        }
return singletonObject;
    }
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
    }
public void beforeSingletonCreation(String beanName) {
this.singletonsCurrentlyInCreation.add(beanName);
    }
public void afterSingletonCreation(String beanName) {
this.singletonsCurrentlyInCreation.remove(beanName);
this.singletonObjects.put(beanName, this.earlySingletonObjects.remove(beanName));
    }
}

应用实践

示例1:基本循环依赖

java复制代码
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}

在这个例子中,A和B相互依赖,Spring通过三级缓存机制能够成功解决循环依赖问题。

示例2:AOP代理下的循环依赖

java复制代码
@Component
public class A {
@Autowired
private B b;
}
@Component
@Aspect
public class B {
@Autowired
private A a;
}

在这个例子中,B是一个切面,Spring会在创建B的代理对象时处理循环依赖问题。

优缺点

优点

  • 简化依赖管理:通过IoC容器管理Bean的依赖关系,降低了代码耦合度。
  • 提高可测试性:方便使用Mock对象进行单元测试。
  • 解决循环依赖:通过三级缓存机制有效解决了循环依赖问题。

缺点

  • 性能损耗:三级缓存机制增加了Bean的创建成本。
  • 复杂性增加:对于开发者来说,理解三级缓存机制需要一定的学习成本。

总结

通过本文的深入剖析,相信你对Spring IoC循环依赖的底层源码有了全新的认识。Spring通过三级缓存机制巧妙地解决了循环依赖问题,使得开发者能够更加方便地管理Bean的依赖关系。同时,我们也看到了Spring在解决复杂问题时所展现出的智慧和优雅。

相关文章
|
7天前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
97 69
|
6天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
40 21
|
12天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
12天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
11天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
2月前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
4月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
234 24
|
存储 缓存 Java
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?
在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。
497 0
|
8月前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
132 0
|
存储 缓存 Java
Spring为何需要三级缓存解决循环依赖,而不是二级缓存?
今天给大家分享一道大厂面试真题,Spring为何需要三级缓存解决循环依赖,而不是二级缓存?我一共分为五个部分来给大家介绍: 1、什么是循环依赖? 循环依赖就是指循环引用,是两个或多个Bean相互之间的持有对方的引用。在代码中,如果将两个或多个Bean互相之间持有对方的引用,因为Spring中加入了依赖注入机制,也就是自动给属性赋值。Spring给属性赋值时,将会导致死循环。那么,哪些情况会出现循环依赖呢?
202 0