跳转到内容

refresh() 十二步

refresh() 是 Spring 容器启动的总编排器。它把配置读取、工厂准备、处理器注册、事件基础设施和单例创建串成一个有严格先后关系的生命周期。

先给答案:refresh() 是把“配置对象”推进成“可运行容器”的状态机

Section titled “先给答案:refresh() 是把“配置对象”推进成“可运行容器”的状态机”

refresh() 不是一串初始化代码,而是一组有前置条件的阶段:先创建并准备 BeanFactory,再注册处理器和事件机制,最后预实例化非懒加载单例。顺序的核心原因是,后面的 Bean 创建必须知道前面已经安装了哪些处理器和基础设施。

如果把预实例化提前,Bean 可能在后置处理器注册前就被创建,AOP、注解注入或自定义初始化就会失效;如果失败后不销毁已创建资源,容器重启或测试隔离会留下半成品。阅读 refresh() 要追踪的不是十二个方法名,而是每一步给下一步增加了什么能力。

prepareRefresh
-> obtainFreshBeanFactory
-> prepareBeanFactory
-> postProcessBeanFactory
-> invokeBeanFactoryPostProcessors
-> registerBeanPostProcessors
-> initMessageSource / initApplicationEventMulticaster
-> onRefresh / registerListeners
-> finishBeanFactoryInitialization
-> finishRefresh

入口是 AbstractApplicationContext#refresh() — spring-context/.../AbstractApplicationContext.java:582。各阶段方法位于 :668、:720、:730、:787、:795、:812、:821、:854、:907、:915、:943、:1003。

为什么处理器注册在预实例化之前

Section titled “为什么处理器注册在预实例化之前”

替代方案:先创建全部单例,再注册后置处理器。 为什么不行:已经创建的对象不会重新经过依赖注入、代理和初始化回调,导致同一容器中出现处理器生效与不生效两种 Bean。 证据:finishBeanFactoryInitialization() 最终调用 DefaultListableBeanFactory#preInstantiateSingletons() — spring-beans/.../DefaultListableBeanFactory.java:1102;它位于 BPP 注册之后。

阶段 作用 关键坐标
准备环境 校验活动状态、初始化属性 AbstractApplicationContext.java:668
获取工厂 创建或刷新内部 BeanFactory :720
注册基础设施 注册环境、资源、事件发布器等依赖 :730
执行 BFPP 修改 BeanDefinition :795
注册 BPP 参与后续 Bean 创建 :812
初始化事件/消息 建立上下文级基础设施 :821、:854
预实例化 创建非懒加载单例 :943
完成刷新 生命周期事件、LiveBeansView 等 :1003

refresh() 使用外层异常边界保证刷新失败时调用 cancelRefresh() 和 closeBeans()。这使得“启动失败”不是半初始化状态继续对外提供服务,而是释放已创建资源后将上下文置为不可用。

场景 现象 原因 规避
BFPP 中依赖业务 Bean Bean 提前实例化且错过部分 BPP 修改定义和创建实例混在一起 BFPP 只处理元数据
非懒加载 Bean 初始化失败 整个上下文启动失败 预实例化是刷新完成的前置条件 检查依赖、配置和初始化回调
onRefresh() 中启动外部服务 刷新失败后残留线程 外部资源未纳入销毁回调 实现生命周期接口并保证关闭幂等

复杂启动流程应显式划分“准备、注册、创建、提交”四类阶段;只有最后提交成功,系统才应该对外宣告 ready。

面试锚点

  • refresh() 为什么先执行 BFPP 再注册 BPP?
  • 哪一步真正创建非懒加载单例?
  • 刷新失败时 Spring 如何避免留下半初始化上下文?