一文搞懂Spring循环依赖的原理

一文搞懂Spring循环依赖的原理

Spring容器中的循环依赖是指两个或多个Bean彼此之间相互依赖。这种情况下,容器就无法完成Bean实例化,从而导致应用程序无法正常启动。因此,解决循环依赖问题是Spring框架中一个非常重要的问题。

循环依赖的概念

循环依赖是指两个或多个Bean之间出现了相互依赖的情况。例如:Bean1依赖于Bean2,而Bean2又依赖于Bean1。如果没有合理的解决方案,循环依赖会导致Spring容器无法完成Bean的实例化,从而导致应用程序无法正常启动。

Bean的实例化过程

在讲解 Spring的循环依赖之前,我们需要了解一下Spring框架中Bean的实例化过程。简单来说,Bean的实例化过程可以分为以下几个步骤:

  1. Spring容器扫描应用程序中的所有Bean定义并解析它们的依赖关系。
  2. 容器按照Bean之间的依赖关系,创建BeanDefinition对象,并将其保存在BeanFactoryRegistry中。
  3. 容器根据BeanDefinition中的信息,通过实例化工厂来创建Bean实例。
  4. 对新创建的Bean实例进行属性注入。
  5. 执行Bean初始化方法。
  6. 将实例化后的Bean对象放入容器缓存中,供其他Bean使用。

Spring循环依赖的原理

Spring为了解决Bean之间的循环依赖,采用了循环依赖的中断和暂时提前暴露两种解决方案。

循环依赖的中断

循环依赖的中断是指当触发循环依赖条件时,Spring会提前暴露一个对象的早期引用来解决依赖问题。

而早期引用的创建可以分为以下两个阶段:

  1. 创建一个用于保存对象的半成品
  2. 进行属性填充

例如:

class A {
  private B b;

  public void setB(B b) {
    this.b = b;
  }  
}

class B {
  private A a;

  public B() {
  }

  public void setA(A a) {
    this.a = a;
  }
}

当容器创建A的BeanDefinition对象时,发现A依赖于B,容器首先会创建一个只有默认构造函数的半成品B。

在创建B对象的过程中,会首先调用其构造函数,并将未完成的B对象注册到容器的缓存中。在B的半成品对象创建成功后,容器会继续开始创建A对象,并将其注入B对象。此时,B对象的半成品中持有的A对象就是未注入之前的空对象。

接着,容器会创建完整的B对象,该对象中已经可以获取到完整的A对象,并将完整的B对象注入A对象中,最终完成Bean的创建。

暂时提前暴露

如果循环依赖是通过构造函数注入的话,Spring采取的就是暂时提前暴露的方式。

例如:

class A {
  private B b;

  public A(B b) {
    this.b = b;
  }
}

class B {
  private A a;

  public B(A a) {
    this.a = a;
  }
}

当A和B引用彼此时,Spring无法创建它们的实例。为了解决该问题,Spring通过“暂时提前暴露”的方式来解决该问题。

具体过程如下:

  1. 容器向构造函数注入B时,Spring会首先创建B的半成品对象。
  2. 在创建过程中,Spring会检查该半成品对象是否出现了循环依赖。
  3. 如果检测到了循环依赖,Spring会先将B的半成品对象放到缓存中。
  4. 等到A的BeanDefinition被解析并注册到容器中时,Spring会利用其来完成B对象的注入。
  5. 此时,B对象已经完成实例化,并可以被注入到A中,完成双向关联。

示例1

为了更好的理解Spring中循环依赖的问题,下面我们以两个相关联的DAO对象为例来演示它的实现过程。

首先定义两个DAO:

public class UserDaoImpl implements UserDao {
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

注意:在UserDaoImpl类中依赖UserService接口,在UserServiceImpl类中依赖UserDao接口。当这两个对象被注入时,就会发现它们之间存在循环依赖。

Spring的循环依赖处理过程如下:

  1. Spring创建UserDao的BeanDefinition对象,发现它依赖于UserService接口。
  2. Spring创建UserService的BeanDefinition对象,发现它依赖于UserDao接口。
  3. 此时,Spring会首先创建一个UserDao的半成品对象。
  4. 等到UserService的完整Bean创建完成后,Spring会使用它完成UserDao的依赖注入。
  5. UserService完整Bean创建完成后,Spring开始创建UserDao对象,完成UserService的依赖注入。

示例2

接下来,我们再来看一个使用构造函数注入的例子。

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

在上面这个例子中,B依赖于A,A依赖于B。如果使用setter方法来注入依赖关系,那么Spring能够成功解决循环依赖的问题,但使用构造函数注入时,就需要采用“提前暴露”的方式来解决依赖问题。

具体过程如下:

  1. Spring创建B的BeanDefinition对象并检查依赖关系。
  2. 发现它依赖于A,因此首先创建A的半成品对象。
  3. 等到B对象完整Bean创建之后,Spring会使用其完成A的依赖注入。

总结:以上就是解决Spring中循环依赖问题的两种方法,能够有效避免应用程序启动时循环依赖导致的问题。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一文搞懂Spring循环依赖的原理 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • springboot整合shardingjdbc实现分库分表最简单demo

    下面是一份完整的SpringBoot整合ShardingJDBC实现分库分表最简单demo的攻略: 一、前置条件 掌握SpringBoot和Maven的基础及配置方式; 了解什么是ShardingJDBC以及其分库分表的实现原理; 准备好使用的数据库及其账号密码。 二、添加依赖 在Maven的pom.xml文件中添加以下依赖: <dependency&…

    Java 2023年5月20日
    00
  • MyBatis自定义映射关系和关联查询实现方法详解

    MyBatis自定义映射关系和关联查询实现方法详解 简介 MyBatis是一款支持自定义SQL、存储过程和高级映射的持久层框架。在开发过程中,有时需要在查询结果中嵌套查询结果,或者查询结果中的列与实体类中的属性名不匹配。这就需要用到MyBatis自定义映射关系和关联查询。 MyBatis自定义映射关系 MyBatis中自定义映射关系可以通过ResultMap…

    Java 2023年5月20日
    00
  • SpringMVC实现注解式权限验证的实例

    针对“SpringMVC实现注解式权限验证的实例”的完整攻略,我们可以按照以下步骤进行: 1. 添加依赖 在 pom.xml 中添加以下依赖: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</a…

    Java 2023年6月16日
    00
  • JDBC对MySQL数据库布尔字段的操作方法

    JDBC是Java Database Connectivity的缩写,是Java语言中处理各种关系型数据库的标准应用程序接口。通过JDBC接口,开发人员可以使用Java语言对数据库进行增、删、改、查的各种操作。本文将针对MySQL数据库中的布尔字段,在JDBC中进行操作的方法,提供一些实用示例。 1. 驱动程序的引入 要使用JDBC对MySQL数据库的操作,…

    Java 2023年6月16日
    00
  • java实现电话本系统

    Java实现电话本系统攻略 1. 系统概述 Java实现电话本系统,是指使用Java编程语言和相关的开发框架实现一个方便用户管理联系人信息的系统。系统的目标是支持联系人的增删改查、分组管理、导入导出、备份恢复等功能。具体而言,系统将包括以下模块: 用户登录和注册:为用户提供账号管理功能,增强系统的安全性; 联系人管理:用户可以查看、添加、删除、修改联系人的信…

    Java 2023年5月19日
    00
  • Java JTable 实现日历的示例

    这里提供一个Java JTable 实现日历的示例的完整攻略: 1. 实现一个基本的日历 步骤一:创建一个 JFrame,并添加一个 JTable,用来显示日历 public class Calendar extends JFrame { private final int WIDTH = 600; private final int HEIGHT = 40…

    Java 2023年5月20日
    00
  • 十五道tomcat面试题,为数不多的机会!

    下面我将分步骤介绍“十五道tomcat面试题,为数不多的机会!”的完整攻略。 一、了解Tomcat Tomcat是一个简单的、易于使用的Web服务器,也是一个Servlet容器。它是开源的,由Apache软件基金会维护。可以运行在Windows、Linux、Unix等多个平台上。 二、准备Tomcat面试题 为了确保你能顺利通过Tomcat的面试,你需要提前…

    Java 2023年5月19日
    00
  • 使用Spring Data JDBC实现DDD聚合的示例代码

    使用Spring Data JDBC实现DDD聚合的示例代码是一个比较复杂的过程,需要在DDD(领域驱动设计)的思想指导下,设计实现聚合及其关联的实体、值对象等等。以下是一个完整的攻略: 一、设计实体和聚合 首先需要确定需要实现的实体和聚合,并了解其业务含义和关系。 示例一:订单聚合 假设我们设计的一个电商系统,需要实现订单聚合,聚合中包含订单及其关联的商品…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部