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

《Spring Bean 是怎么被创建出来的?容器启动流程全景分析》

大家好呀!今天我们要聊一个特别重要的话题——Spring框架中Bean的生命周期和上下文启动原理。我知道很多小伙伴一听到"生命周期"、"上下文"这些词就头大,别担心!我会用做奶茶🍵的流程来比喻,保证连小学生都能听懂!(๑•̀ㅂ•́)و✧

一、先来认识Spring容器这个大管家 🏰

想象Spring容器就像一个超级智能的奶茶店🏪,而Bean就是店里制作的各种奶茶🧋。这个奶茶店可不简单,它知道:

  1. 需要准备哪些原料(Bean定义)
  2. 什么时候煮珍珠(Bean初始化)
  3. 怎么搭配最好喝(依赖注入)
  4. 什么时候该倒掉过期的奶茶(Bean销毁)

1.1 Spring容器的两大代表

Spring主要有两种容器:

  1. BeanFactory:基础版奶茶店,只提供最基本的奶茶制作服务

    BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
    MilkTea mt = (MilkTea) factory.getBean("milkTea");
    
  2. ApplicationContext:豪华版奶茶店,除了基本功能还有:

    • 国际化的海报(国际化支持)
    • 自动促销活动(事件发布)
    • 会员系统集成(资源访问)
    • 等等…
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    MilkTea mt = context.getBean(MilkTea.class);
    

💡 实际开发中我们基本都用ApplicationContext,就像现在谁还去只有基本功能的小奶茶店呢?( ̄▽ ̄)*

二、Bean生命周期的完整旅程 🚀

一个Bean从无到有要经历好多阶段呢!让我们用制作珍珠奶茶的过程来类比:

2.1 阶段一:准备食谱(Bean定义)

首先要把奶茶配方登记到系统中:

  1. XML配置方式(传统菜谱):

  2. 注解方式(电子菜谱):

    @Component
    public class MilkTea {@Value("50%")private String sweetness;
    }
    
  3. 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开始往里面加料(依赖注入):

  1. 通过setter方法加糖:

    milkTea.setSweetness("50%");
    
  2. 或者通过构造器直接做甜度刚好的:

    public MilkTea(String sweetness) {this.sweetness = sweetness;
    }
    

2.4 阶段四:初始化——摇一摇更好喝! 🧊

这时候奶茶基本做好了,但还要进行最后加工:

  1. BeanNameAware:告诉奶茶它叫什么名字

    public void setBeanName(String name) {this.beanName = name; // 比如"milkTea"
    }
    
  2. BeanFactoryAware:告诉奶茶它在哪个工厂

    public void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;
    }
    
  3. @PostConstruct:加冰摇匀!

    @PostConstruct
    public void shakeWell() {System.out.println("摇啊摇~奶茶更好喝!");
    }
    
  4. InitializingBean:检查味道

    public void afterPropertiesSet() {if(this.sweetness == null) {throw new RuntimeException("忘记加糖啦!");}
    }
    
  5. init-method:最后的装饰

    
    

2.5 阶段五:使用中——客人享用时 😋

现在Bean已经准备好被使用了:

MilkTea myTea = context.getBean(MilkTea.class);
myTea.drink(); // 啊~真好喝!

2.6 阶段六:销毁——打烊清理啦! ♻️

当容器关闭时:

  1. @PreDestroy:先把没喝完的倒掉

    @PreDestroy
    public void pourOut() {System.out.println("倒掉剩余奶茶...");
    }
    
  2. DisposableBean:洗杯子

    public void destroy() {System.out.println("认真清洗杯子中...");
    }
    
  3. destroy-method:关机器

    
    

三、图解Bean生命周期 📊

为了更清晰,来看个完整流程图:

开始 → 实例化 → 属性赋值 → 初始化前(Aware接口) → 初始化(@PostConstruct) 
→ 初始化后(InitializingBean) → 使用中 → 销毁前(@PreDestroy) → 销毁(DisposableBean) → 结束

就像奶茶的完整流程:
买原料 → 煮茶 → 加料 → 摇匀 → 装杯 → 卖给客人 → 倒掉剩余 → 洗杯子 → 关店

四、ApplicationContext的启动过程 🔄

现在来看看豪华奶茶店是怎么开门的:

4.1 启动步骤详解

  1. 准备阶段:开店前的准备

    // 相当于选址开店
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    
  2. 刷新容器:正式营业(refresh()方法)

    • 准备BeanFactory:清理旧店,准备新器材
    • 加载Bean定义:读取所有奶茶配方
    • 准备BeanFactory后处理:调整设备参数
    • 执行BeanFactoryPostProcessor:特殊定制配方
    • 注册BeanPostProcessor:聘请专业调茶师
    • 初始化消息源:准备多语言菜单
    • 初始化事件广播器:安装店内广播
    • 子类特殊处理:特色区域准备
    • 注册监听器:安排试喝员
    • 实例化单例Bean:开始制作招牌奶茶
    • 完成刷新:正式开门营业!
  3. 运行阶段:接待客人

    // 客人点单
    context.getBean("milkTea");
    
  4. 关闭阶段:打烊

    context.close(); // 收拾店铺
    

4.2 关键扩展点

Spring提供了很多可以插手的地方:

  1. BeanFactoryPostProcessor:修改配方

    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {// 比如把所有的甜度改成30%}
    }
    
  2. 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有什么区别?

特点BeanFactoryApplicationContext
实例化时机懒加载启动时预加载
功能基本功能扩展功能(事件、国际化等)
自动注册不支持支持BeanPostProcessor等自动注册
资源消耗较少较多

简单说:BeanFactory是小卖部,ApplicationContext是大型超市!🛒

5.2 Spring如何解决循环依赖?

想象两个奶茶:

  • 奶茶A说:我要加奶茶B的珍珠
  • 奶茶B说:我要加奶茶A的布丁

Spring的解决办法:

  1. 先做半成品A(实例化但不初始化)
  2. 再做半成品B
  3. 把半成品A给B完成
  4. 再用完成的B完成A

就像:

  1. 先泡好茶A但不加料
  2. 泡好茶B
  3. 把A的茶给B加料
  4. 再用B的料完成A
// 三级缓存解决循环依赖
// 一级缓存:成品奶茶
// 二级缓存:半成品奶茶(正在加工)
// 三级缓存:奶茶工厂(能生产奶茶)

5.3 @PostConstruct和init-method的执行顺序?

它们的执行顺序是:

  1. @PostConstruct
  2. InitializingBean的afterPropertiesSet()
  3. init-method

就像喝奶茶的步骤:

  1. 先插吸管(@PostConstruct)
  2. 再搅一搅(InitializingBean)
  3. 最后拍照(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的生命周期,就像跟踪一杯奶茶的完整制作过程:

  1. 准备阶段:登记配方(Bean定义)
  2. 实例化:煮茶底(创建实例)
  3. 属性赋值:加各种料(依赖注入)
  4. 初始化:摇匀装饰(各种初始化回调)
  5. 使用期:服务顾客(业务使用)
  6. 销毁期:清理店铺(资源释放)

记住几个关键点:

  • 生命周期回调就像奶茶制作的各个质检环节 ✅
  • 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 的“暗坑”与解决方案(二)

http://www.xdnf.cn/news/791191.html

相关文章:

  • 小体积涵盖日常办公等多功能的软件
  • MyBatis实战项目测试
  • 2025.6.3学习日记 Nginx 基本概念 配置 指令 文件
  • React-native之Flexbox
  • nginx 如何禁用tls1.0
  • CSS radial-gradient函数详解
  • JVM-内存结构
  • MAU算法流程理解
  • VueUse:组合式API实用函数全集
  • ADI硬件笔试面试题型解析上
  • DevEco Studio的使用
  • VUE组件库开发 八股
  • 时态--10--被动语态
  • Selenium 中 JavaScript 点击操作的原理及应用
  • Java:跨越时代的编程语言,持续引领技术革新
  • IPython 使用技巧整理
  • 强化学习鱼书(10)——更多深度强化学习的算法
  • Spring AI 项目实战(一):Spring AI 核心模块入门
  • 【Linux】Linux 进程基础
  • 华为港城 RAG 推理训练新突破:过程监督助力 5k 样本性能超越 90k 模型
  • 神经符号集成-三篇综述
  • COMSOL多边形骨料堆积混凝土水化热传热模拟
  • shell脚本总结13:head -c 和cut -c的区别
  • C++ 中的依赖注入(Dependency Injection)
  • Lua和JS的继承原理
  • 【PhysUnits】15.12 去Typenum库的SI 单位制词头实现(prefix.rs)
  • pycharm如何查看git历史版本变更信息
  • AI地面垃圾检测算法智能分析网关V4打造城市/公园/校园等场景环保卫生监管解决方案
  • MySQL 日志数据同步的详细教程
  • Message=“HalconDotNet.HHandleBase”的类型初始值设定项引发异常