一文搞懂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日

相关文章

  • C#编程自学之开篇介绍

    C#编程自学之开篇介绍 本文将为大家介绍如何通过自学的方式学习C#编程语言。C#是一种面向对象的程序设计语言,它主要用于开发Windows桌面应用程序、Web应用程序、游戏、移动应用程序等应用领域。相信大家在学习C#编程过程中会遇到各种各样的问题,如何处理这些问题是自学过程中最关键的一点。 确定学习C#编程的目的和方向 在开始自学之前,首先需要明确自己想要学…

    Java 2023年5月23日
    00
  • 简单了解JAVA构造方法

    简单了解JAVA构造方法 什么是构造方法 Java中每个类都有构造方法,构造方法是用来初始化对象的方法,即在使用new操作符创建对象时调用的一种特殊方法。构造方法与类名相同,无需返回类型,且不能被重载。 构造方法的特点 构造方法名要与类名相同,且区分大小写; 构造方法没有返回值类型; 构造方法没有具体的返回值,但需要使用return语句结束构造方法; 构造方…

    Java 2023年5月26日
    00
  • Java多态的全面系统解析

    Java多态的全面系统解析 什么是多态 多态(Polymorphism)是面向对象编程中一个非常重要的概念,指的是同类对象的不同表现形式。具体而言,多态是指在运行时根据实际类型来确定对象的实际行为。 Java中的多态可以分为两种:编译时多态和运行时多态。 编译时多态,也称为静态多态,是指在编译时就能确定具体的方法调用。这种多态是通过Java的方法重载实现的。…

    Java 2023年5月23日
    00
  • Java实现帧动画的实例代码

    下面是Java实现帧动画的实例代码的完整攻略: 什么是帧动画 帧动画是指通过在一定时间内连续播放多张图像帧来形成动画效果,每张图像帧都是唯一的,它们按照预设的顺序播放,这样我们就可以看到连续的动态效果了。 实现思路 Java实现帧动画的基本思路是利用Java中的Timer类定期刷新,将预先设定好的多张图片按照一定的时间间隔连续显示出来,达到帧动画的效果。 具…

    Java 2023年5月18日
    00
  • java中创建、写入文件的5种方式

    当我们在开发Java应用程序时,可能会遇到需要将数据写入文件的需求,本文将介绍Java中创建、写入文件的5种方式。 1. 使用FileOutputStream和BufferedOutputStream创建和写入文件 使用Java的FileOutputStream和BufferedOutputStream类,我们可以创建和写入文件: import java.i…

    Java 2023年5月19日
    00
  • Ubuntu 16.04安装Apache Tomcat的方法

    下面是Ubuntu 16.04安装Apache Tomcat的具体步骤: 步骤一:安装Java环境 在Ubuntu 16.04中,可以通过以下命令安装Java环境: sudo apt-get update sudo apt-get install default-jdk 安装成功后,可以通过以下命令验证Java版本信息: java -version 示例输出…

    Java 2023年5月19日
    00
  • Spring Security实现自动登陆功能示例

    下面是详细讲解Spring Security实现自动登陆功能的完整攻略。 什么是Spring Security Spring Security是Spring框架中的模块,它处理安全性和认证的方面。它可以与Spring应用程序的其他部分(如Spring MVC)无缝集成,从而使开发人员可以轻松地将安全性添加到他们的应用程序中。 自动登录功能的实现原理 自动登录…

    Java 2023年5月20日
    00
  • Tomcat+JDK安装和配置教程

    下面是Tomcat+JDK安装和配置教程的完整攻略: 1. 下载JDK和Tomcat 首先需要下载JDK和Tomcat。可以在以下官网下载: JDK下载页面:https://www.oracle.com/java/technologies/javase-downloads.html Tomcat下载页面:https://tomcat.apache.org/d…

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