从简入繁介绍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的循环依赖,但最好还是避免设计出这种相互依赖的情况,以保持代码的清晰和易于管理。

相关文章
|
5月前
|
XML 人工智能 Java
优化SpringBoot程序启动速度
本文介绍了三种优化SpringBoot启动速度的方法:1) 延迟初始化Bean,通过设置`spring.main.lazy-initialization`为true,将耗时操作延后执行;2) 创建扫描索引,利用`spring-context-indexer`生成@ComponentScan的索引文件,加速类扫描过程;3) 升级至最新版SpringBoot,享受官方性能优化成果。这些方法能显著提升程序编译与启动效率。
1390 0
|
3月前
|
安全 Java 开发者
Java集合框架:详解Deque接口的栈操作方法全集
理解和掌握这些方法对于实现像浏览器后退功能这样的栈操作来说至关重要,它们能够帮助开发者编写既高效又稳定的应用程序。此外,在多线程环境中想保证线程安全,可以考虑使用ConcurrentLinkedDeque,它是Deque的线程安全版本,尽管它并未直接实现栈操作的方法,但是Deque的接口方法可以相对应地使用。
236 12
|
2月前
|
XML Java 数据格式
Bean的生命周期:从Spring的子宫到坟墓
Spring 管理 Bean 的生命周期,从对象注册、实例化、属性注入、初始化、使用到销毁,全程可控。Bean 的创建基于配置或注解,Spring 在容器启动时扫描并生成 BeanDefinition,按需实例化并填充依赖。通过 Aware 回调、初始化方法、AOP 代理等机制,实现灵活扩展。了解 Bean 生命周期有助于更好地掌握 Spring 框架运行机制,提升开发效率与系统可维护性。
|
机器学习/深度学习 人工智能 自然语言处理
|
8月前
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
7月前
|
Java 关系型数据库 MySQL
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
1220 12
|
设计模式 Java Spring
BeanFactory与FactoryBean的区别
BeanFactory与FactoryBean的区别
224 0
|
NoSQL 算法 关系型数据库
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
本文详解分布式全局唯一ID及其5种实现方案,关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
|
11月前
|
Java 数据库连接 数据库
Spring Batch 中的 Tasklet 是什么?
Spring Batch 中的 Tasklet 是什么?
555 2
|
存储 监控 Java
JVM 元空间(Metaspace)
JVM 元空间(Metaspace)
1450 5