前言:很多新手朋友学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的痛点是显而易见的:
- 耦合度极高:业务代码和具体的类实现绑死了,如果想替换UserDao的实现类,必须修改业务代码。
- 繁琐且重复:每用一个对象就要new一次,依赖层级深时,创建对象的代码会极其冗余。
- 不便于单元测试:因为对象是硬编码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 会先完成配置解析与元数据注册:
将配置信息(XML / 注解 / Java 配置)解析为BeanDefinition对象
所有的 BeanDefinition 存储在beanDefinitionMap(一个 HashMap)中
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 接口 | 回调方法 | 作用 |
|---|---|---|
BeanNameAware | setBeanName(String name) | 获取自身的 Bean ID/name |
BeanFactoryAware | setBeanFactory(BeanFactory) | 获取 BeanFactory 容器 |
ApplicationContextAware | setApplicationContext(ApplicationContext) | 获取 ApplicationContext 容器 |
第五阶段:BeanPostProcessor 前置处理
在初始化正式开始之前,Spring 会调用所有BeanPostProcessor的postProcessBeforeInitialization()方法。
这是一个极其强大的扩展点。Spring AOP 的代理对象就是在此阶段生成的。
💡BeanPostProcessor贯穿整个 Bean 生命周期,是 Spring 最强大的扩展机制。
第六阶段:初始化(Initialization)
这是 Bean 生命周期中最具技术含量和复杂度的阶段。初始化按固定顺序执行以下步骤:
Aware 接口注入(如 BeanNameAware、BeanFactoryAware 等)
@PostConstruct注解方法(Java EE 标准,推荐使用)InitializingBean.afterPropertiesSet()(Spring 特有接口,与 Spring API 耦合,不推荐)自定义
init-method(通过@Bean(initMethod = "xxx")或 XML 配置)
初始化顺序总结:Aware 接口 → @PostConstruct → InitializingBean → 自定义 init-method
第七阶段:BeanPostProcessor 后置处理
初始化完成后,Spring 会调用所有BeanPostProcessor的postProcessAfterInitialization()方法。
此时 Bean 已初始化完毕,可以进行最终的修饰,例如后续处理、监控代码的植入等。
第八阶段:就绪使用(Ready for Use)
此时 Bean已经完全创建、配置并初始化完毕,驻留在容器中(通常是单例池singletonObjects),等待应用程序的使用。
第九阶段:销毁(Destruction)
当容器正常关闭时,Bean 进入销毁阶段。销毁顺序为:
@PreDestroy注解方法DisposableBean.destroy()自定义
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,否则一律使用构造器注入。
五、总结
我们来把今天所有的知识点串联起来回顾一下:
为什么要用Bean?因为原生
new对象耦合高、测试难,Spring通过IoC(控制反转)帮我们接管了对象的创建权。Bean的一生经历了什么?经历了“定义 → 实例化(New) → 属性填充(依赖注入) → Aware回调 → 前置处理 → 初始化(@PostConstruct→Init) → 后置处理 → 就绪 → 销毁”。
依赖怎么注入?优先用构造器注入,保证依赖不可变和线程安全;其次用Setter注入处理可选依赖;尽量避免使用字段注入。
理解透了Bean的生命周期,你就不再只是会用框架的“搬运工”,而是真正懂Spring内核的“工程师”了。下次遇到诡异的NullPointerException(空指针异常),你就知道该去生命周期的哪个阶段排查了!