《Spring Bean 是怎么被创建出来的?容器启动流程全景分析》
大家好呀!今天我们要聊一个特别重要的话题——Spring框架中Bean的生命周期和上下文启动原理。我知道很多小伙伴一听到"生命周期"、"上下文"这些词就头大,别担心!我会用做奶茶🍵的流程来比喻,保证连小学生都能听懂!(๑•̀ㅂ•́)و✧
一、先来认识Spring容器这个大管家 🏰
想象Spring容器就像一个超级智能的奶茶店🏪,而Bean就是店里制作的各种奶茶🧋。这个奶茶店可不简单,它知道:
- 需要准备哪些原料(Bean定义)
- 什么时候煮珍珠(Bean初始化)
- 怎么搭配最好喝(依赖注入)
- 什么时候该倒掉过期的奶茶(Bean销毁)
1.1 Spring容器的两大代表
Spring主要有两种容器:
-
BeanFactory:基础版奶茶店,只提供最基本的奶茶制作服务
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); MilkTea mt = (MilkTea) factory.getBean("milkTea");
-
ApplicationContext:豪华版奶茶店,除了基本功能还有:
- 国际化的海报(国际化支持)
- 自动促销活动(事件发布)
- 会员系统集成(资源访问)
- 等等…
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); MilkTea mt = context.getBean(MilkTea.class);
💡 实际开发中我们基本都用ApplicationContext,就像现在谁还去只有基本功能的小奶茶店呢?( ̄▽ ̄)*
二、Bean生命周期的完整旅程 🚀
一个Bean从无到有要经历好多阶段呢!让我们用制作珍珠奶茶的过程来类比:
2.1 阶段一:准备食谱(Bean定义)
首先要把奶茶配方登记到系统中:
-
XML配置方式(传统菜谱):
-
注解方式(电子菜谱):
@Component public class MilkTea {@Value("50%")private String sweetness; }
-
Java配置方式(厨师口述):
@Configuration public class TeaConfig {@Beanpublic MilkTea milkTea() {return new MilkTea("50%");} }
2.2 阶段二:实例化——煮奶茶啦! 🧋
容器看到配方后就开始制作了:
// 相当于 new MilkTea(),但实际是通过反射创建的
MilkTea milkTea = (MilkTea) beanFactory.getBean("milkTea");
这时候奶茶还是白开水,什么料都没加呢!(⊙ˍ⊙)
2.3 阶段三:属性赋值——加料时间到! 🧋
Spring开始往里面加料(依赖注入):
-
通过setter方法加糖:
milkTea.setSweetness("50%");
-
或者通过构造器直接做甜度刚好的:
public MilkTea(String sweetness) {this.sweetness = sweetness; }
2.4 阶段四:初始化——摇一摇更好喝! 🧊
这时候奶茶基本做好了,但还要进行最后加工:
-
BeanNameAware:告诉奶茶它叫什么名字
public void setBeanName(String name) {this.beanName = name; // 比如"milkTea" }
-
BeanFactoryAware:告诉奶茶它在哪个工厂
public void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory; }
-
@PostConstruct:加冰摇匀!
@PostConstruct public void shakeWell() {System.out.println("摇啊摇~奶茶更好喝!"); }
-
InitializingBean:检查味道
public void afterPropertiesSet() {if(this.sweetness == null) {throw new RuntimeException("忘记加糖啦!");} }
-
init-method:最后的装饰
2.5 阶段五:使用中——客人享用时 😋
现在Bean已经准备好被使用了:
MilkTea myTea = context.getBean(MilkTea.class);
myTea.drink(); // 啊~真好喝!
2.6 阶段六:销毁——打烊清理啦! ♻️
当容器关闭时:
-
@PreDestroy:先把没喝完的倒掉
@PreDestroy public void pourOut() {System.out.println("倒掉剩余奶茶..."); }
-
DisposableBean:洗杯子
public void destroy() {System.out.println("认真清洗杯子中..."); }
-
destroy-method:关机器
三、图解Bean生命周期 📊
为了更清晰,来看个完整流程图:
开始 → 实例化 → 属性赋值 → 初始化前(Aware接口) → 初始化(@PostConstruct)
→ 初始化后(InitializingBean) → 使用中 → 销毁前(@PreDestroy) → 销毁(DisposableBean) → 结束
就像奶茶的完整流程:
买原料 → 煮茶 → 加料 → 摇匀 → 装杯 → 卖给客人 → 倒掉剩余 → 洗杯子 → 关店
四、ApplicationContext的启动过程 🔄
现在来看看豪华奶茶店是怎么开门的:
4.1 启动步骤详解
-
准备阶段:开店前的准备
// 相当于选址开店 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
-
刷新容器:正式营业(refresh()方法)
- 准备BeanFactory:清理旧店,准备新器材
- 加载Bean定义:读取所有奶茶配方
- 准备BeanFactory后处理:调整设备参数
- 执行BeanFactoryPostProcessor:特殊定制配方
- 注册BeanPostProcessor:聘请专业调茶师
- 初始化消息源:准备多语言菜单
- 初始化事件广播器:安装店内广播
- 子类特殊处理:特色区域准备
- 注册监听器:安排试喝员
- 实例化单例Bean:开始制作招牌奶茶
- 完成刷新:正式开门营业!
-
运行阶段:接待客人
// 客人点单 context.getBean("milkTea");
-
关闭阶段:打烊
context.close(); // 收拾店铺
4.2 关键扩展点
Spring提供了很多可以插手的地方:
-
BeanFactoryPostProcessor:修改配方
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {// 比如把所有的甜度改成30%} }
-
BeanPostProcessor:加工奶茶
public class MyBeanPostProcessor implements BeanPostProcessor {public Object postProcessBeforeInitialization(Object bean, String beanName) {// 初始化前加点料return bean;}public Object postProcessAfterInitialization(Object bean, String beanName) {// 初始化后包装一下return new Proxy(bean);} }
五、常见面试题精讲 💼
5.1 BeanFactory和ApplicationContext有什么区别?
特点 | BeanFactory | ApplicationContext |
---|---|---|
实例化时机 | 懒加载 | 启动时预加载 |
功能 | 基本功能 | 扩展功能(事件、国际化等) |
自动注册 | 不支持 | 支持BeanPostProcessor等自动注册 |
资源消耗 | 较少 | 较多 |
简单说:BeanFactory是小卖部,ApplicationContext是大型超市!🛒
5.2 Spring如何解决循环依赖?
想象两个奶茶:
- 奶茶A说:我要加奶茶B的珍珠
- 奶茶B说:我要加奶茶A的布丁
Spring的解决办法:
- 先做半成品A(实例化但不初始化)
- 再做半成品B
- 把半成品A给B完成
- 再用完成的B完成A
就像:
- 先泡好茶A但不加料
- 泡好茶B
- 把A的茶给B加料
- 再用B的料完成A
// 三级缓存解决循环依赖
// 一级缓存:成品奶茶
// 二级缓存:半成品奶茶(正在加工)
// 三级缓存:奶茶工厂(能生产奶茶)
5.3 @PostConstruct和init-method的执行顺序?
它们的执行顺序是:
- @PostConstruct
- InitializingBean的afterPropertiesSet()
- init-method
就像喝奶茶的步骤:
- 先插吸管(@PostConstruct)
- 再搅一搅(InitializingBean)
- 最后拍照(init-method) 📸
六、实际开发小技巧 🛠️
6.1 如何优雅地管理Bean初始化?
@Component
public class DatabaseInitializer {@PostConstructpublic void init() {try {// 初始化数据库连接} catch (Exception e) {throw new RuntimeException("初始化失败", e);}}
}
6.2 如何实现Bean的延迟初始化?
@Lazy
@Component
public class HeavyService {// 这个Bean只有在第一次被使用时才会初始化
}
6.3 如何监听上下文事件?
@Component
public class MyContextListener implements ApplicationListener {public void onApplicationEvent(ContextRefreshedEvent event) {// 当容器刷新完成时执行System.out.println("奶茶店准备好啦!快来买吧~");}
}
七、总结 🎯
今天我们详细讲解了Spring Bean的生命周期,就像跟踪一杯奶茶的完整制作过程:
- 准备阶段:登记配方(Bean定义)
- 实例化:煮茶底(创建实例)
- 属性赋值:加各种料(依赖注入)
- 初始化:摇匀装饰(各种初始化回调)
- 使用期:服务顾客(业务使用)
- 销毁期:清理店铺(资源释放)
记住几个关键点:
- 生命周期回调就像奶茶制作的各个质检环节 ✅
- ApplicationContext启动过程就像奶茶店的开业准备 🏪
- 各种Aware接口让Bean知道自己的"身份信息" 🆔
- BeanPostProcessor就像奶茶店的品控专员 🕵️♂️
下次当你喝奶茶的时候,不妨想想Spring Bean的生命周期,是不是觉得编程和做奶茶也有异曲同工之妙呢?(。♥‿♥。)
推荐阅读文章
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
什么是 Cookie?简单介绍与使用方法
-
什么是 Session?如何应用?
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
如何理解应用 Java 多线程与并发编程?
-
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
如何理解线程安全这个概念?
-
理解 Java 桥接方法
-
Spring 整合嵌入式 Tomcat 容器
-
Tomcat 如何加载 SpringMVC 组件
-
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
-
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
-
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
-
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
-
Java 中消除 If-else 技巧总结
-
线程池的核心参数配置(仅供参考)
-
【人工智能】聊聊Transformer,深度学习的一股清流(13)
-
Java 枚举的几个常用技巧,你可以试着用用
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
-
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)