spring循环依赖以及补充相关知识

在spring项目中会出现Bean a需要Bean b,Bean b需要Bean a这样互相依赖的问题,我们去讨论深层次的原因和解决办法

前置知识

初始化和实例化

  • 实例化 = 创建对象(在内存里造出一个真实的对象)
  • 初始化 = 给对象赋值(给造好的对象填初始值)

1. 实例化(Instantiation)

定义 :通过 new 关键字,根据类创建一个真实的对象

  • 类是图纸,实例化就是按图纸造出一台真实的手机
  • 只做一件事:在内存中开辟空间,生成对象

代码示例

复制代码
// 1. 定义一个类(图纸)
class Phone {
    String brand;
}

// 2. 实例化:创建对象(造手机)
Phone phone = new Phone(); 

new Phone() 这一步就是实例化


2. 初始化(Initialization)

定义 :给实例化后的对象设置初始值

  • 手机造出来了,初始化就是给它设置品牌、颜色、价格
  • 目的:让对象有可用的初始状态。

代码示例

复制代码
// 先实例化
Phone phone = new Phone(); 

// 再初始化:给属性赋值
phone.brand = "苹果"; 

brand 赋值就是初始化

Bean和对象

对于Bean和对象是两个东西,其中所有 Bean 都是对象,但不是所有对象都是 Bean。

  • 对象 :Java 里 new 出来的东西
  • Bean被 Spring 容器管理的对象

被 Spring 容器创建、管理、控制生命周期的对象,才叫 Bean。实例化,初始化,三级缓存,销毁等这些是bean才有的行为

对比项 普通对象 Spring Bean
创建者 自己 new Spring 容器
生命周期 自己管 Spring 全程管理
依赖注入 手动 set 自动 @Autowired
AOP 代理 不会自动变成代理 自动生成代理对象
销毁 没人管,等 GC 容器关闭时销毁
作用域 singleton、prototype 等

spring容器

关于spring容器,可以说Spring 容器 = 实现了 IOC 思想的那个 "Bean 工厂 / Bean 管理器"

1. IOC 是什么?

控制反转(Inversion of Control)

  • 以前:你自己 new 对象、管依赖
  • 现在:把创建对象、管理依赖的权力交给 Spring
  • 这种 "控制权反转" 的思想,就叫 IOC

2. Spring 容器是什么?

就是具体实现 IOC 的那个东西

  • 它帮你创建 Bean
  • 帮你注入依赖
  • 帮你管理生命周期
  • 帮你解决循环依赖
  • 帮你生成代理

所以大家口语里经常说:

IOC 容器 = Spring 容器


3. 对应到代码里

  • IOC:思想 / 设计原则
  • BeanFactory / ApplicationContext:Spring 容器的具体实现
  • @Service、@Autowired:基于 IOC 容器的用法

4. 最直白的类比

  • IOC = 外包制度(思想)
  • Spring 容器 = 外包公司(具体干活的)

你不用自己招人(new 对象),外包公司(容器)帮你管人、发工资、安排工作,这种模式就叫 IOC。

bean的销毁和垃圾回收

  • Bean 的销毁 :是 Spring 容器层面的主动销毁、资源释放
  • 垃圾回收(GC) :是 JVM 层面的内存自动回收

Bean 销毁 ≠ 立即被 GCBean 被 GC 前,一定会先经历销毁

行为 执行者 目的 时机
Bean 销毁 Spring 容器 关闭资源、清理 容器关闭 / 移除 Bean 时
垃圾回收 GC JVM 释放堆内存 无引用 + 内存不足时

Bean 的销毁,只在 Spring 容器关闭的时候执行。(Spring 容器关闭,就是 Java 应用程序正常退出的时候。 只有正常退出才会关闭容器、执行 Bean 销毁;强行杀死进程,容器来不及关闭。)

Bean的生命周期

要想了解为什么会有循环依赖,先了解一下bean的生命周期

如图所示,一个Bean需要先要先通过Beandefinition获取bean的信息然后通过构造函数实例化Bean,到此是实例化过程,下一步是Bean的依赖注入,aware接口注入,Bean的后置处理器前置,初始化方法,Bean的后置处理器后置,最后是销毁bean。

循环依赖

依赖产生的原因和过程

  • 构造方法注入的循环依赖 → 无解,会直接报错,spring解决不了,@Lazy 能解决 (Bean实例化阶段)
  • setter / 字段注入的循环依赖(Bean生命周期的依赖注入) → Spring 用三级缓存自动解决(Bean初始化阶段)
  1. 构造方法注入时
java 复制代码
@Component
public class A {
    // 构造器依赖 B
    public A(B b) {}
}

@Component
public class B {
    // 构造器依赖 A
    public A(A a) {}
}
  1. setter /field 注入时(属性填充阶段)
java 复制代码
@Component
public class A {
    @Autowired
    private B b; // 属性依赖
}

@Component
public class B {
    @Autowired
    private A a;
}

@lazy

在构造器注入时,不创建真实 Bean ,而是创建一个动态代理对象当作占位符传进去。通过这样的方式解决循环依赖

而是创建一个动态代理对象当作占位符传进去。

复制代码
public A(@Lazy B b) { }
  • 构造器拿到代理B→ A 顺利实例化
  • 之后再正常创建 B → B 可以拿到完整 A
  • 第一次调用 B 的方法时,代理才去创建真实 B

作用

  • 绕过实例化阶段的死锁
  • 用代理占位,让构造方法能执行完
  • 真实 Bean 延迟到使用时才创建

三级缓存

  • 一级缓存(singletonObjects)完全初始化好的成品 Bean。(经历完了初始化阶段)

  • 二级缓存(earlySingletonObjects)早期半成品 Bean(可能是代理)。(经历完了实例化阶段但是没有经历完初始化阶段)

  • 三级缓存(singletonFactories)ObjectFactory 对象工厂,用于延迟生成早期 Bean / 代理 。(完成了实例化阶段还没有发生依赖注入时就把bean的ObjectFactory放入三级缓存里面了)

极简过程

  • 实例化 A
  • 把 A 的工厂放入三级缓存
  • → 开始给 A 注入属性(发现要 B)(依赖注入)
  • 实例化 B
  • → 把 B 的工厂放入三级缓存
  • → 给 B 注入 A → 从三级缓存拿 A 工厂,生成早期 A
  • → A放入二级缓存,删除三级A的工厂
  • B 初始化完成→ 放入一级缓存
  • A 初始化完成 → 放入一级缓存
相关推荐
繁星星繁2 小时前
Docker(一)
java·c语言·数据结构·c++·docker·容器·eureka
编程大师哥2 小时前
JAVA 动态代理
java·开发语言
圣光SG2 小时前
Java类与对象及面向对象基础核心详细笔记
java·前端·数据库
白露与泡影2 小时前
从 BIO 到 epoll:高并发 I/O 模型演进与本质分析
java·服务器·数据库
学编程就要猛2 小时前
JavaEE进阶:Spring Boot快速上手
java·spring boot·java-ee
shark22222222 小时前
springboot中配置logback-spring.xml
spring boot·spring·logback
csdn2015_2 小时前
HashSet 和 LinkedHashSet 区别
java·开发语言
KoiHeng2 小时前
初识Maven
java·maven