楼层: 首页/ 软件技术/ Spring 核心框架/ Bean 的生命周期:从出生到销毁
3

Bean 的生命周期:从出生到销毁

Bean Lifecycle

Spring 容器启动时,Bean 不是"new 完就完事",它要走一整套流程:实例化 → 属性注入 → 初始化 → 就绪使用 → 销毁。搞懂这条流水线,你才能解释"为什么 @PostConstruct 里拿不到代理对象""为什么 @Lazy 能解决启动慢"这类玄学问题。

论Bean 一生的五个阶段

① 实例化(Instantiation):Spring 调用构造方法,把对象在内存里 new 出来。此时属性还是 null。

② 属性注入(Populate):把 @Autowired 标注的依赖、@Value 标注的值,一个个塞进对象的字段。

③ 初始化(Initialization):回调 @PostConstruct 方法、执行 InitializingBean.afterPropertiesSet()、调用 init-method。这时 Bean 已经能用了。

④ 使用(In Use):你从容器里 getBean 出来,正常调用。整个应用运行期间都在这一步。

⑤ 销毁(Destruction):容器关闭时回调 @PreDestroy、DisposableBean.destroy()、destroy-method。清理资源。

用 @PostConstruct 和 @PreDestroy 感受一下生命周期

package com.example.bean; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import org.springframework.stereotype.Component; @Component public class ConnectionHolder { // 初始化回调:属性注入完成后自动调用 @PostConstruct public void open() { System.out.println(">> 打开连接池"); } // 销毁回调:容器关闭前自动调用 @PreDestroy public void close() { System.out.println(">> 关闭连接池"); } }
容器启动时: >> 打开连接池 容器关闭时: >> 关闭连接池

三种初始化/销毁回调,用哪个

回调方式写法评价
@PostConstruct / @PreDestroy注解标在方法上,Jakarta 标准。推荐 最简单,零侵入。
InitializingBean / DisposableBean实现接口,重写 afterPropertiesSet() / destroy()。了解 与 Spring 耦合,不推荐。
init-method / destroy-methodXML 或 @Bean 上指定方法名。老项目 XML 时代的做法。

新项目一律用 @PostConstruct 和 @PreDestroy,别再去实现那些接口。

Bean 作用域:单例、原型、请求、会话

Spring 默认 Bean 是 singleton(单例)——整个容器里只有一个实例,所有人共享。但有些对象每次用都得新造一个,那就用 prototype(原型)。Web 环境下还有 request、session 这种跟请求周期绑定的作用域。

作用域含义典型场景
singleton整个容器一个实例(默认)。Service、Dao、配置类——无状态对象。
prototype每次 getBean 都 new 一个新的。有状态对象,比如表单 Bean、任务实例。
request每个 HTTP 请求一个实例。Web 层,请求级上下文。
session每个用户会话一个实例。购物车、登录态。

切换作用域和懒加载

import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; // 默认 singleton。改成 prototype:每次注入都是新对象 @Component @Scope("prototype") public class TaskInstance { ... } // @Lazy:容器启动时不创建,第一次用到才创建 // 启动慢的项目里,把一些不常用的 Bean 标上 @Lazy 能显著加快启动 @Component @Lazy public class ReportGenerator { ... }

BeanPostProcessor:Spring 自己的"切面"

Spring 内部大量使用 BeanPostProcessor——在 Bean 初始化前后插手,对 Bean 做加工。AOP 代理、@Autowired 注入、@PostConstruct 回调,底层都是 BeanPostProcessor 实现的。你自己写框架时也会用到它。

一个打日志的 BeanPostProcessor

import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class LoggingBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { // 初始化之前调用:Bean 已经 new 完、属性已注入 return bean; // 返回原对象或包装后的对象 } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { // 初始化之后调用:AOP 代理就是在这里生成的 return bean; } }
singleton 注入 prototype 的坑

单例 Bean 里注入原型 Bean,原型只会被注入一次!因为单例在容器启动时就创建好了,它持有的那个 prototype 引用永远是第一次注入的那个。想每次都拿到新的,要么让 singleton 实现 ApplicationContextAware 每次 getBean,要么用 @Lookup 注解。新手踩这个坑能怀疑人生。

本章面试题 · 第 3 章(Bean 生命周期)

1.(流程题)请按顺序说一个 Spring Bean 从创建到销毁走了哪些步骤?

查看答案

答案:实例化(调构造器)→ 属性填充(@Autowired 注入)→ Aware 接口回调(BeanNameAware 等)→ BeanPostProcessor 前置处理 → 初始化(@PostConstruct、afterPropertiesSet、init-method)→ BeanPostProcessor 后置处理(AOP 代理就在这一步生成)→ 就绪使用 → 销毁(@PreDestroy、destroy-method)。解析:能说出"后置处理器在初始化前后各调一次、AOP 代理在这一步织入",面试官就知道你懂底层。

2.(高频题)什么是循环依赖?Spring 的三级缓存分别存了什么?为什么构造器循环依赖解决不了?

查看答案

答案:A 依赖 B、B 又依赖 A 就是循环依赖。三级缓存:一级 singletonObjects(成品单例)、二级 earlySingletonObjects(提前暴露的半成品)、三级 singletonFactories(对象工厂,用来生成早期引用,AOP 场景靠它决定要不要提前织入代理)。解析:三级缓存只解决单例的 setter/字段注入循环依赖;构造器循环依赖是"对象还没 new 完就要求 new 别人",逻辑上死锁,无解,只能重新设计。

3.(思考题)为什么 Spring 要用三级缓存,两级不够吗?

查看答案

答案:关键在于 AOP。如果 B 要注入的是 A 的代理对象,三级缓存的工厂能保证"提前暴露出去的就是最终的代理对象",不会出现"先暴露原始对象、后来又换成代理"的不一致。两级缓存做不到这一点。解析:一句话:三级缓存是为了在有 AOP 代理时,也能优雅地暴露早期引用。