当前位置: 首页 > news >正文

Spring Bean 生命周期终极指南:从“手动New”到“容器掌控”全流程详解

前言:很多新手朋友学Spring时,总是被“Bean生命周期”劝退,感觉一堆接口和注解绕来绕去。其实,理解它最关键的一步,是先想明白“我们为什么需要Spring来管对象?”

看完这篇,你不仅能搞懂Bean的“生老病死”,还能彻底弄明白@Autowired到底干了啥,面试官再问生命周期,直接跟他画流程图!

一、开篇:从Java原生“New对象”的痛点说起

在讲Spring Bean之前,我们先来回顾一下,在纯粹的Java SE时代,我们是如何创建使用一个对象的。

  • 首先,你得定义一个Java类。
// 学生类 public class Student{ // 学号 private String sid; // 姓名 private String name; // 年龄 private int age; public Student(String sid , String name, int age){ this.sid = sid; this.name = name; this.age = age; } }
  • 其次,在业务代码中,你必须要通过new关键字手动去实例化这个对象。
public class App{ public static void mian(String[] args){ // 实例化学生对象 Student student = new Student("2291","李明",20); } }
  • 最麻烦的是,如果这个类里面引用了其他类的对象(依赖),你在new这个类之前,还得先把里面依赖的那些类全部new出来,然后作为构造参数塞进去。
public class Teacher{ // 教师编号 private String tid; // 教师姓名 private String name; // 教师年龄 private int age; // 课代表【依赖学生对象】 private Student subjectRepre; // 第一种注入方式,在构造函数中直接new public Teacher(){ this.tid = "123"; this.name = "王"; this.age = 30; // 在构造函数中通过new去注入依赖 this.subjectRepre = new Student("3325","小红",16); } // 第二种注入方式,作为构造函数的参数进行注入 public Teacher(String tid,String name,int age , Student subjectRepre){ this.tid = tid; this.name = name; this.age = age; this.subjectRepre = subjectRepre; } }

上述代码还只是依赖一个类,如果依赖多个类,我们就必须手动new多个对象。

这种手动New的痛点是显而易见的:

  1. 耦合度极高:业务代码和具体的类实现绑死了,如果想替换UserDao的实现类,必须修改业务代码。
  2. 繁琐且重复:每用一个对象就要new一次,依赖层级深时,创建对象的代码会极其冗余。
  3. 不便于单元测试:因为对象是硬编码new出来的,很难在测试时用虚拟对象(Mock)去替换。

二、IoC 容器:从“手动挡”到“自动挡”的进化

通过上面的代码示例,我们已经切身感受到了手动new对象的痛苦。那有没有一种方法,能把我们从这种繁琐的“手动挡”操作中彻底解放出来呢?

让我们先来做一番设想:如果有一个“神级帮手”,它能帮我们做下面这些事情,那该多好!

1. 第一步:标记 —— 告诉帮手“我是一个需要被创建的类”,我们把这个标记先称之为Bean

我们只需要在类上面加一个注解(比如叫@MyBean),告诉它:“嘿,程序启动的时候记得把我创建出来!”

@MyBean public class Student { private String sid; private String name; private int age; // 省略构造方法和 getter/setter } @MyBean public class Teacher { private String tid; private String name; private int age; // 我想用 Student,但我懒得自己 new! private Student subjectRepre; }

2. 第二步:依赖标记 —— 告诉帮手“我需要谁”

当某个类需要依赖另一个类时,我们再添加一个依赖注解(比如叫@MyInject),告诉它:“把这个依赖帮我注入进来!”

@MyBean public class Teacher { // ... 其他属性 @MyInject // 告诉帮手:我需要一个 Student 对象,帮我找找! private Student subjectRepre; }

3. 第三步:帮手自动工作 —— 扫描 → 创建 → 注入

有了上面的标记,这个“神级帮手”在项目启动时,就会自动完成以下工作:

步骤帮手做什么技术手段
① 扫描扫描项目中所有类,找到带有@MyBean的类类路径扫描(Classpath Scanning)
② 实例化通过反射机制,自动创建所有被标记类的实例Java 反射(Reflection API)
③ 保管将创建好的实例统一存放进一个“仓库”中通常是一个Map<String, Object>结构
④ 注入依赖扫描所有实例中带有@MyInject的字段,从仓库中取出对应的实例,通过 Setter 方法或反射暴力注入依赖注入(DI)

这个过程用一张图(deepseek生成)来表示就是:

有了这个神级帮手,有趣的事情便出现了,对象——创建,依赖注入,销毁等权限从我们程序员手中转到了这个神级助手手中了,我们程序员从此后便不在需要关注一个实例化的对象要如何创建,只需要关注如何去定义这个对象的创建工作,通俗点说就是:“你别管对象怎么来的,只管用就行,我帮你把一切都安排好。”。而这就是Spring的核心思想之一,IoC(Inversion of Control)—— 控制反转

4. 控制反转(IoC)到底“反转”了什么?

很多初学者看到“控制反转”这四个字会觉得抽象,我们来拆解一下:

在传统编程中,程序流是由开发者编写的代码主动控制的:

“我需要对象 → 我自己 new → 我需要依赖 → 我自己传参”

而在 IoC 模式下,控制权被“反转”了:

“我需要对象 → 容器帮我 new → 我需要依赖 → 容器自动帮我注入”

对比传统模式IoC 模式
谁控制对象的创建?开发者自己容器(Spring)
谁控制依赖的注入?开发者自己容器(Spring)
谁控制对象的销毁?开发者自己容器(Spring)
开发者做什么?控制一切只需关注业务逻辑

说白了,就是把“控制权”从你的手里,反转给了容器。

5. IoC 带来的核心优势

优势说明
解耦业务代码不再依赖具体实现类,只依赖接口或抽象。想换实现?改配置就行,代码不用动!
简化开发开发者只需关注“做什么”(业务逻辑),无需关心“怎么创建依赖”(繁琐的new操作)。
便于测试由于依赖由容器注入,单元测试时可以轻松用 Mock 对象替换真实依赖,无需启动完整容器。
统一管理所有 Bean 的生命周期(创建→使用→销毁)由容器统一把控,便于做 AOP、事务管理等高级功能。

三、重头戏:Spring Bean 的完整生命周期

1、Bean 生命周期全景概览

Spring Bean 的生命周期是指Bean 从创建到销毁所经历的完整过程。核心可以概括为四个大阶段

实例化 → 属性注入(依赖注入)→ 初始化 → 销毁

如果把生命周期细分,可以拆解为七个关键阶段

实例化 → 属性填充 → BeanNameAware 回调 → BeanFactoryAware 回调 → 初始化前(@PostConstruct)→ 初始化方法 → 初始化后

接下来,我们逐阶段深入拆解。

2、Bean 创建所经历的完整阶段

第一阶段:Bean 定义与加载(元数据阶段)

在 Bean 真正被创建之前,Spring 会先完成配置解析与元数据注册

  1. 将配置信息(XML / 注解 / Java 配置)解析为BeanDefinition对象

  2. 所有的 BeanDefinition 存储在beanDefinitionMap(一个 HashMap)中

  3. BeanDefinition 中包含了 Bean 的类名、作用域、依赖关系、初始化方法名等核心元数据

这个阶段 Bean 还没有被创建,仅仅存在“元数据定义”,类似于建造工厂的蓝图。

第二阶段:实例化(Instantiation)

容器通过反射调用 Bean 的构造函数(或工厂方法)来创建对象实例。

此时创建出来的只是一个“空对象”,属性均为默认值。相当于:

MyBean bean = new MyBean();

单例 Bean 实例化后会先存入三级缓存(singletonFactories),为解决循环依赖做准备。

第三阶段:属性填充 / 依赖注入(Populate Properties)

这是连接实例化与初始化的关键桥梁

容器会解析 Bean 的依赖关系(通过@Autowired@Value、XML 中的<property>等配置),并将所需的依赖(其他 Bean、基本值、集合等)注入到对象的属性中

相当于:

bean.setDependency(someOtherBean);

此时 Bean 从“空壳”变成了具备完整依赖关系的对象

第四阶段:Aware 接口回调

如果 Bean 实现了各种Aware 接口,容器会回调相应的方法,将一些基础设施对象注入给 Bean:

Aware 接口回调方法作用
BeanNameAwaresetBeanName(String name)获取自身的 Bean ID/name
BeanFactoryAwaresetBeanFactory(BeanFactory)获取 BeanFactory 容器
ApplicationContextAwaresetApplicationContext(ApplicationContext)获取 ApplicationContext 容器
第五阶段:BeanPostProcessor 前置处理

在初始化正式开始之前,Spring 会调用所有BeanPostProcessorpostProcessBeforeInitialization()方法。

这是一个极其强大的扩展点。Spring AOP 的代理对象就是在此阶段生成的。

💡BeanPostProcessor贯穿整个 Bean 生命周期,是 Spring 最强大的扩展机制。

第六阶段:初始化(Initialization)

这是 Bean 生命周期中最具技术含量和复杂度的阶段。初始化按固定顺序执行以下步骤:

  1. Aware 接口注入(如 BeanNameAware、BeanFactoryAware 等)

  2. @PostConstruct注解方法(Java EE 标准,推荐使用)

  3. InitializingBean.afterPropertiesSet()(Spring 特有接口,与 Spring API 耦合,不推荐)

  4. 自定义init-method(通过@Bean(initMethod = "xxx")或 XML 配置)

初始化顺序总结:Aware 接口 → @PostConstruct → InitializingBean → 自定义 init-method

第七阶段:BeanPostProcessor 后置处理

初始化完成后,Spring 会调用所有BeanPostProcessorpostProcessAfterInitialization()方法。

此时 Bean 已初始化完毕,可以进行最终的修饰,例如后续处理、监控代码的植入等。

第八阶段:就绪使用(Ready for Use)

此时 Bean已经完全创建、配置并初始化完毕,驻留在容器中(通常是单例池singletonObjects),等待应用程序的使用。

第九阶段:销毁(Destruction)

当容器正常关闭时,Bean 进入销毁阶段。销毁顺序为:

  1. @PreDestroy注解方法

  2. DisposableBean.destroy()

  3. 自定义destroy-method

四、依赖注入方法详解

依赖注入(DI)发生在生命周期的第三阶段(属性填充)。Spring 提供了多种依赖注入方式:

1. 构造器注入(Constructor Injection)⭐ 推荐

通过类的构造函数来注入依赖,在对象创建时即完成所有依赖的注入。

@Service public class UserService { private final UserRepository userRepository; @Autowired // Spring 4.3+ 可省略 public UserService(UserRepository userRepository) { this.userRepository = userRepository; } } // 或 @Service @RequiredArgsConstructor // lombok包中对手工@Autowired的自动化处理 public class UserService { private final UserRepository userRepository; }

优点:

  • 依赖可以声明为final线程安全且不可变。

  • 依赖缺一不可,对象创建出来就是完全初始化好的,杜绝NPE。

  • 单元测试极其方便,直接new UserService(mockDao)即可,无需启动Spring。

2. Setter 方法注入(Setter Injection)

通过类的Setter 方法来注入依赖。容器在调用无参构造函数实例化 Bean 之后,再调用 Setter 方法。

@Service public class OrderService { private PaymentService paymentService; @Autowired public void setPaymentService(PaymentService paymentService) { this.paymentService = paymentService; } }

适用场景:该依赖不是必需的(可有可无),或者允许在运行时重新注入。

3. 字段注入(Field Injection)⚠️ 谨慎使用

通过在字段上直接使用@Autowired注解来实现,Spring 容器利用反射机制直接设置字段的值。

@Service public class UserService { @Autowired // 字段注入 private UserRepository userRepository; }

缺点(为什么IDEA会警告?)

  • 依赖无法声明为final线程安全性有隐患。

  • 依赖对类外部完全透明,看不出这个类依赖了啥,破坏封装性

  • 单元测试必须依赖Spring容器启动,难以进行纯Java的Mock测试

📢生产环境铁律严禁使用字段注入(@Autowired写在属性上)!除非是写Demo,否则一律使用构造器注入。

五、总结

我们来把今天所有的知识点串联起来回顾一下:

  1. 为什么要用Bean?因为原生new对象耦合高、测试难,Spring通过IoC(控制反转)帮我们接管了对象的创建权。

  2. Bean的一生经历了什么?经历了“定义 → 实例化(New) → 属性填充(依赖注入) → Aware回调 → 前置处理 → 初始化(@PostConstruct→Init) → 后置处理 → 就绪 → 销毁”

  3. 依赖怎么注入?优先用构造器注入,保证依赖不可变和线程安全;其次用Setter注入处理可选依赖;尽量避免使用字段注入

理解透了Bean的生命周期,你就不再只是会用框架的“搬运工”,而是真正懂Spring内核的“工程师”了。下次遇到诡异的NullPointerException(空指针异常),你就知道该去生命周期的哪个阶段排查了!

http://www.jsqmd.com/news/1183224/

相关文章:

  • MCP 系列(05):Resources 和 Prompts 进阶——动态数据、参数化 URI 与多轮模板
  • 自动化微型模组降本方案,微型压力传感器品牌推荐,广东犸力性价比替代海外产品 - 品牌速递
  • 如何选择最适合你的AI模型:Qwen2.5-7B-Instruct_rai_1.7.1_npu_4K与其他主流LLM模型全面对比指南
  • 亨得利官方钟表服务中心|全部地址与客服热线权威信息公示(2026年7月更新) - 亨得利官方
  • ModelSim 仿真记录:基于地址帧 UART 的 FPGA 多外设控制系统
  • Claude API构建Shopify智能客服与退货自动化系统实战
  • 计算机毕业设计之基于springboot药房药品管理系统的设计与实现
  • 2026年长春阳光房如何选?这份靠谱攻略助你选到心仪款! - 优选新闻
  • RouterOS DDNS 方案对比:原生Cloud、脚本API与3大服务商适配
  • 智搜 GEO 对比传统 SEO、竞价,三大核心优势解析
  • 2026 年 7 月昆山屋顶外墙防水测评|正规工程防水商家实力排名 - 苏易房屋修缮
  • Arduino UNO硬件设计原理深度解析:从芯片选型到PCB布局
  • Playnite游戏库管理器:一站式管理您所有的PC游戏
  • Mac鼠标增强工具终极指南:让10美元鼠标超越Apple触控板的5个实用技巧
  • 2026汕头房产中介推荐:了解值得信赖的中介公司! - 企业品牌
  • Power BI 视觉对象深度对比:折线图、仪表、切片器等5种核心图表应用场景与性能分析
  • OpenClaw架构详解:一只“龙虾”如何征服10万+GitHub Stars
  • OBS Studio输出模块架构设计与RTMP流媒体实现原理深度解析
  • Davinci Developer的Port Interfaces
  • 退房委托书公证办理流程及相关注意事项,2026 年汇总 - 跑政通
  • OmegaFold实战:从零部署到精准预测蛋白三维结构
  • Dart之数据类型
  • 银行业务分析中的七种生产级pandas多维聚合模式
  • ChatGPT服务不可用?OpenAI官方状态页不会告诉你的5个隐藏信号——从CDN缓存击穿到Rate Limit动态阈值突变(含实时监控Dashboard模板)
  • PyCharm 报错 ‘No Interpreter’ 深度排查:3类根因分析与5步修复流程
  • Unity UGUI事件穿透进阶:从新手引导到复杂UI交互的完整解决方案
  • 非线性规划WebApp求解:AI驱动的智能建模优化全过程平台
  • LuaJIT字节码反编译终极指南:5分钟快速恢复Lua源代码
  • 2026版AI大模型行业风口解析+零基础入门实战全攻略
  • mlx-community/Qwen3.5-2B-OptiQ-4bit架构深入分析:混合精度量化实现原理