从简入繁介绍springbean的循环依赖

简介: 【4月更文挑战第11天】Spring Bean的循环依赖是Spring框架中一个比较复杂且有趣的话题,涉及到Spring的核心容器、Bean生命周期和依赖注入机制。我将从简入繁地介绍这一概念,确保即使是初学者也能理解。

Spring Bean的循环依赖是Spring框架中一个比较复杂且有趣的话题,涉及到Spring的核心容器、Bean生命周期和依赖注入机制。我将从简入繁地介绍这一概念,确保即使是初学者也能理解。

什么是循环依赖?

在Spring中,循环依赖指的是两个或多个Bean互相持有对方的引用,形成闭环。比如,Bean A依赖于Bean B,同时Bean B又依赖于Bean A。

循环依赖的类型

  • 构造器循环依赖:通过构造器注入方式产生的循环依赖。这种依赖是无法解决的,因为在调用构造器之前,Bean就必须被创建。
  • 字段注入循环依赖:通过字段(属性)注入方式产生的循环依赖。Spring可以通过三级缓存来解决这种循环依赖。
  • 方法注入循环依赖:通过设置方法注入方式产生的循环依赖,同样可以通过三级缓存解决。

Spring是如何解决循环依赖的?

Spring容器解决循环依赖主要依赖于其“三级缓存”机制。这里涉及到Spring容器创建Bean的过程,具体分为以下几步:

  1. 实例化Bean:首先,Spring容器调用构造函数或工厂方法创建Bean的实例,此时Bean还未初始化(即没有填充属性、没有调用生命周期回调等)。
  2. 一级缓存:然后,这个原始的Bean实例被放入到一级缓存(singletonObjects)中,此缓存存放的是完成初始化的Bean。
  3. 二级缓存:如果这个Bean实例有代理增强(如AOP),Spring会创建Bean的代理,并将这个代理放入二级缓存(earlySingletonObjects)中。
  4. 三级缓存:同时,一个对象工厂被放入三级缓存(singletonFactories),这个工厂负责生成Bean的代理对象。

解决循环依赖的关键点

  • 当Bean A创建时,它被加入到三级缓存中。
  • 如果Bean A依赖Bean B,在创建Bean B的过程中需要Bean A,此时可以从三级缓存中获取到Bean A的早期引用(可能是代理对象),从而避免了循环依赖的问题。
  • 一旦Bean A和Bean B都创建完成,它们就会从三级、二级缓存移动到一级缓存中,此时它们都是完全初始化后的对象。

从简入繁的说明

  • 简单理解:循环依赖就像是“我等你,你等我”,但Spring通过一种巧妙的“先给你个空壳(三级缓存的工厂对象),等我弄好了再填充完整内容”来解决这个问题。
  • 进阶理解:理解Spring的Bean生命周期,知道Spring如何在创建Bean的过程中使用三级缓存机制来解决字段注入和方法注入产生的循环依赖。
  • 深入探讨:深入Spring的源码,了解Spring容器是如何具体实现这一过程的,包括Bean的创建过程、三级缓存的具体作用以及如何在运行时操作这些缓存。

注意

  • 构造器注入的循环依赖是无法通过Spring框架解决的,因为在构造函数调用之前就需要依赖的Bean准备好,这在逻辑上是不可能实现的。
  • Spring默认支持解决单例作用域Bean的循环依赖,对于原型作用域Bean的循环依赖,Spring不会尝试解决。


以下是具体的实现步骤和代码示例。

1. 创建Spring Boot项目

首先,使用Spring Initializr(https://start.spring.io/)创建一个新的Spring Boot项目,选择Web作为依赖。

2. 定义两个互相依赖的Bean

我们将创建两个类ClassAClassB,并使它们通过Spring的自动装配特性(@Autowired注解)互相引用对方。

ClassA.java:

java复制代码

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassA {

    private ClassB classB;

    @Autowired
    public void setClassB(ClassB classB) {
        this.classB = classB;
    }

    public String getMessage() {
        return "Message from ClassA";
    }
}

ClassB.java:

java复制代码

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassB {

    private ClassA classA;

    @Autowired
    public void setClassA(ClassA classA) {
        this.classA = classA;
    }

    public String getMessage() {
        return "Message from ClassB";
    }
}

在这个例子中,ClassA依赖于ClassB,而ClassB也依赖于ClassA。它们通过@Autowired注解和设置器(setter)方法来实现依赖注入,形成了一个循环依赖。

3. 创建一个测试Controller

为了验证循环依赖是否被成功解决,我们可以创建一个简单的REST控制器,来注入ClassA并调用其方法。

DemoController.java:

java复制代码

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    private final ClassA classA;

    @Autowired
    public DemoController(ClassA classA) {
        this.classA = classA;
    }

    @GetMapping("/test")
    public String test() {
        return classA.getMessage() + " & " + classA.getClassB().getMessage();
    }
}

解释

在这个Spring Boot应用中,当Spring容器启动时,它会尝试创建ClassAClassB的实例。由于使用了字段注入(通过设置器方法),Spring可以通过其内部的循环依赖处理机制来成功实例化这两个互相依赖的Bean。

当请求/test端点时,DemoController将返回ClassAClassB的消息,证明循环依赖被成功解决了。

这个示例展示了Spring是如何处理Bean之间的循环依赖问题,特别是通过字段注入或设置器方法注入。值得注意的是,尽管Spring能够解决单例Bean的循环依赖,但最好还是避免设计出这种相互依赖的情况,以保持代码的清晰和易于管理。

相关文章
|
6月前
|
XML 缓存 Java
Spring源码之 Bean 的循环依赖
循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示: 代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢? 可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。 一、复现循环依赖问题 Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖 1. 基于xml复现循环依赖 定义实体 Bean java复制代码public class A {
|
6月前
|
缓存 Java Spring
Spring循环依赖原理和Bean创建过程
Spring循环依赖原理和Bean创建过程
100 0
|
缓存 Java Spring
Spring框架(四) 三级缓存与循环依赖
首先我们需要明白什么是循环依赖 , 打个比方 , 就是说A对象在创建的过程中 , 需要依赖注入B对象 , 但是B对象没有 , 就需要去创建 , 而在创建B对象的过程中又需要注入A对象 , A对象此时还在创建中,所以就构成了一个死循环 , A,B相互依赖 这样的关系被成为循环依赖(当然 , 可能还会有其他的情况),下面我们就来看看Spring是如何让解决循环依赖的
157 0
|
缓存 Java Spring
【Spring使用三级缓存解决循环依赖的过程】
【Spring使用三级缓存解决循环依赖的过程】
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
29 1
|
6月前
springBean注入循环解决
springBean注入循环解决
|
4月前
|
传感器
循环依赖问题之Bean循环依赖的定义是什么
循环依赖问题之Bean循环依赖的定义是什么
循环依赖问题之Bean循环依赖的定义是什么
|
4月前
|
Java Spring
Spring循环依赖问题之构造器内的循环依赖如何解决
Spring循环依赖问题之构造器内的循环依赖如何解决
|
4月前
|
Java Spring 容器
循环依赖问题之实例化Bean是通过如何实现的
循环依赖问题之实例化Bean是通过如何实现的
|
4月前
|
存储 缓存 Java
Spring循环依赖问题之循环依赖异常如何解决
Spring循环依赖问题之循环依赖异常如何解决