Spring源码之 Bean 的循环依赖

简介: 循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示:代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢?可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。一、复现循环依赖问题Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖1. 基于xml复现循环依赖定义实体 Beanjava复制代码public class A {

循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示:

代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢?

可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。

一、复现循环依赖问题

Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖

1. 基于xml复现循环依赖

  • 定义实体 Bean
java复制代码public class A {
   private B b;
   public B getB() {
      return b;
   }
   public void setB(B b) {
      this.b = b;
   }
}
java复制代码public class B {
   private A a;
   public A getA() {
      return a;
   }
   public void setA(A a) {
      this.a = a;
   }
}
  • 重写 customizeBeanFactory 方法,禁止循环依赖
java复制代码public class NotAllowCircularXmlApplicationContext extends ClassPathXmlApplicationContext {
   public NotAllowCircularXmlApplicationContext(String... configLocations) throws BeansException {
      super(configLocations);
   }
   @Override
   protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
      // 禁止循环依赖
      super.setAllowCircularReferences(false);
      super.customizeBeanFactory(beanFactory);
   }
}
  • 配置 xml 文件
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-2.5.xsd">
   <context:component-scan base-package="com.sff.demo.cycle"/>
   <bean id="a" class="com.sff.demo.cycle.A">
      <property name="b" ref="b"/>
   </bean>
    
    <bean id="b" class="com.sff.demo.cycle.B">
      <property name="a" ref="a"/>
   </bean>
</beans>
java复制代码public class TestAnnoMain {
   public static void main(String[] args) {
      testCycleRef();
   }
   public static void testXmlCycleRef() {
      NotAllowCircularXmlApplicationContext ac = new NotAllowCircularXmlApplicationContext("application-cycle.xml");
      A bean = ac.getBean(A.class);
      System.out.println(bean);
   }
}

运行结果:

2. 基于注解 @Autowired 复现循环依赖

  • 实体定义
java复制代码@Component
public class C {
   @Autowired
   private D d;
}
@Component
public class D {
   @Autowired
   private C c;
}
  • 重写 AnnotationConfigApplicationContext,禁止循环依赖
java复制代码public class NotAllowCircularAnnotationConfigApplicationContext extends AnnotationConfigApplicationContext {
   public NotAllowCircularAnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
      super();
      register(annotatedClasses);
      // 禁止循环依赖
      super.setAllowCircularReferences(false);
      refresh();
   }
}
  • 运行结果
java复制代码public class TestAnnoMain {
   public static void main(String[] args) {
      testCycleRef();
   }
   public static void testCycleRef() {
      NotAllowCircularAnnotationConfigApplicationContext ac = new NotAllowCircularAnnotationConfigApplicationContext(BeanConfig3.class);
      C bean = ac.getBean(C.class);
      System.out.println(bean);
   }
}

二、在 Spring 中是如何解决该问题的呢?

Spring 中利用三级缓存解决循环依赖问题,缓存定义在
DefaultSingletonBeanRegistry 中

java复制代码public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    // 一级缓存,保存 beanName 和 实例化、初始化好的完整 bean 对象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    // 二级缓存,保存 beanName 和 未初始化的 bean 对象
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);  
    // 三级缓存,保存 beanName 和 lambda 表达式 () -> getEarlyBeanReference(beanName, mbd, bean)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

Spring 中利用三级缓存解决循环依赖的根本就是为了避免引用对象的循环创建,那么它是如何实现这一目的呢?

在本文案例中 class A 中 引用 class B ,同时在 class B 中引用 class A,在创建 A 对象时通过 addSingletonFactory方法,

java复制代码addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

在三级缓 singletonFactories.put(beanName,() -> getEarlyBeanReference(beanName, mbd, bean)) 中存放了 A 对象的这样一个 lambda 表达式,可以理解成回调函数。

当在创建 B 对象时,会调用通过 getSingleton 方法调用A对象三级缓存中的 lambda 表达式,也就是getEarlyBeanReference(beanName, mbd, bean) ,在没有AOP代理的情况下,可以直接返回 A 的对象信息,也就 getEarlyBeanReference(beanName, mbd, bean) 的参数 bean 就是提前缓存的 A 对象。

总结: 在 Spring 中 ,对象的创建分为实例化和初始化,实例化好但是未完成初始化的对象可以直接给其他对象引用,所以 Spring 中就把实例化好为初始化好的对象提前暴露出去,让其他对象能够进行引用,就完成了循环依赖的解耦!

作者:大飞学习笔记

链接:
https://juejin.cn/post/7261269922733359159

相关文章
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
912 26
|
11月前
|
监控 安全 Java
解决 Spring Boot 中 SecurityConfig 循环依赖问题的详解
本文详细解析了在 Spring Boot 中配置 `SecurityConfig` 时可能遇到的循环依赖问题。通过分析错误日志与代码,指出问题根源在于 `SecurityConfig` 类中不当的依赖注入方式。文章提供了多种解决方案:移除 `configureGlobal` 方法、定义 `DaoAuthenticationProvider` Bean、使用构造函数注入以及分离配置类等。此外,还讨论了 `@Lazy` 注解和允许循环引用的临时手段,并强调重构以避免循环依赖的重要性。通过合理设计 Bean 依赖关系,可确保应用稳定启动并提升代码可维护性。
864 0
|
12月前
|
Java Maven 微服务
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的 maven 依赖
在项目中使用Swagger2工具时,需导入Maven依赖。尽管官方最高版本为2.8.0,但其展示效果不够理想且稳定性欠佳。实际开发中常用2.2.2版本,因其稳定且界面友好。以下是围绕2.2.2版本的Maven依赖配置,包括`springfox-swagger2`和`springfox-swagger-ui`两个模块。
532 0
|
11月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
635 70
|
7月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
12月前
|
缓存 Java 应用服务中间件
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——依赖导入和Thymeleaf相关配置
在Spring Boot中使用Thymeleaf模板,需引入依赖`spring-boot-starter-thymeleaf`,并在HTML页面标签中声明`xmlns:th=&quot;http://www.thymeleaf.org&quot;`。此外,Thymeleaf默认开启页面缓存,开发时建议关闭缓存以实时查看更新效果,配置方式为`spring.thymeleaf.cache: false`。这可避免因缓存导致页面未及时刷新的问题。
458 0
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
547 7
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
315 0
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
412 12