在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 销毁 ≠ 立即被 GC 但Bean 被 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初始化阶段)
- 构造方法注入时
java
@Component
public class A {
// 构造器依赖 B
public A(B b) {}
}
@Component
public class B {
// 构造器依赖 A
public A(A a) {}
}
- 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 初始化完成 → 放入一级缓存