← 返回题目列表

Spring 容器的启动流程是怎样的?refresh() 方法做了什么?

高频 困难 第 14 / 30 题 更新于 2026/07/26
容器启动refreshIoCBeanDefinition

简化版

Spring 容器的启动核心是 AbstractApplicationContextrefresh() 方法——它是一个「模板方法」,用固定顺序调用十来个步骤,把「一堆配置」变成「一个装满 Bean、可用的容器」。核心步骤依次是:① 准备环境 → ② 创建 BeanFactory 并加载 BeanDefinition(读配置,把 Bean 的定义读进来,但还没造 Bean)→ ③ 执行 BeanFactoryPostProcessor(改 Bean 定义)→ ④ 注册 BeanPostProcessor(准备 Bean 加工器)→ ⑤ 初始化事件多播器和监听器 → ⑥ 实例化所有非懒加载的单例 Bean(真正造 Bean)→ ⑦ 发布容器刷新完成事件。一句话:先读定义、再改定义、再造 Bean、最后就绪

详细版

refresh() 的主要步骤(简化版,按执行顺序):

public void refresh() {
    // 1. 准备刷新:记录启动时间、校验环境变量
    prepareRefresh();
    // 2. 创建 BeanFactory,加载 BeanDefinition(解析配置/注解,注册 Bean 定义)
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    // 3. 配置 BeanFactory(设置类加载器、注册内置 Bean 等)
    prepareBeanFactory(beanFactory);
    // 4. 【钩子】允许子类对 BeanFactory 做后处理
    postProcessBeanFactory(beanFactory);
    // 5. 执行 BeanFactoryPostProcessor(可修改 BeanDefinition,如占位符替换、@Configuration 处理)
    invokeBeanFactoryPostProcessors(beanFactory);
    // 6. 注册 BeanPostProcessor(此时只注册,等实例化 Bean 时才用)
    registerBeanPostProcessors(beanFactory);
    // 7. 初始化国际化 MessageSource
    initMessageSource();
    // 8. 初始化事件多播器 ApplicationEventMulticaster
    initApplicationEventMulticaster();
    // 9. 【钩子】子类初始化特殊 Bean(如 SpringBoot 内嵌 Tomcat 在这里启动)
    onRefresh();
    // 10. 注册事件监听器
    registerListeners();
    // 11. ★ 实例化所有非懒加载的单例 Bean(核心!Bean 在这里被真正创建)
    finishBeanFactoryInitialization(beanFactory);
    // 12. 完成刷新,发布 ContextRefreshedEvent
    finishRefresh();
}

两个关键分水岭

第 2 步:加载 BeanDefinition —— 只有"定义"(图纸),没有 Bean 实例
第 5 步:BeanFactoryPostProcessor —— 改"定义"(此时仍无实例)
─────── 第 11 步是实例化分界线 ───────
第 11 步:finishBeanFactoryInitialization —— 真正 new Bean、注入、初始化

⚠️ refresh() 用了 synchronized 保证容器刷新的线程安全,且是模板方法模式——固定了这十几步的顺序和骨架,其中 postProcessBeanFactoryonRefresh 等是留给子类的钩子(比如 Spring Boot 的 Web 容器在 onRefresh 里启动内嵌 Tomcat)。理解「refresh 是模板方法、步骤顺序固定」,才能把各扩展点的执行时机对上。

完整版教学

一、refresh() 是什么:从配置到可用容器的总流程

启动 Spring 容器(new ClassPathXmlApplicationContext() 或 Spring Boot 的 SpringApplication.run()),最终都会调到 AbstractApplicationContext.refresh()。它是整个容器启动的「总指挥」:

输入:一堆配置(XML / @Configuration / 注解扫描)
      ↓ refresh() 一步步处理
输出:一个装满了 Bean、依赖关系已注入、可以对外提供服务的容器

refresh() 的名字叫「刷新」,是因为容器可以被多次刷新(重新加载配置)。它是一个模板方法——把「启动容器」这件事拆成固定顺序的十来个步骤,每步职责单一,有些步骤还是留给子类的钩子。理解 refresh 的价值在于:Spring 的所有核心机制(BeanDefinition 加载、后置处理器、Bean 实例化、事件)都在这条流水线的特定位置发生,搞懂它就搞懂了 Spring 启动的全貌。

二、第一阶段:加载 BeanDefinition(读图纸)

启动的第一件大事是把配置解析成 BeanDefinition(第 2 步 obtainFreshBeanFactory)。BeanDefinition 是「Bean 的定义/图纸」——记录了这个 Bean 是哪个类、什么作用域、有哪些属性、依赖谁,但此时一个 Bean 实例都还没创建

配置来源              →  解析成 BeanDefinition
@Component/@Service     组件扫描,扫到就注册一个 BeanDefinition
@Bean 方法             每个 @Bean 注册一个 BeanDefinition
XML <bean>             解析 XML 注册 BeanDefinition

结果:BeanFactory 里有了一堆 BeanDefinition(图纸),但还没造 Bean

关键认知:这一步只是「登记」——把「要造哪些 Bean、怎么造」记录下来,还没真正造。这是理解后续步骤的基础——很多扩展点(BeanFactoryPostProcessor)就是趁「图纸都登记好了、但还没造」这个窗口去改图纸的。

三、第二阶段:BeanFactoryPostProcessor 改图纸

图纸都登记好后,第 5 步执行所有 BeanFactoryPostProcessor(BFPP),让它们在「Bean 实例化之前」修改 BeanDefinition:

时机:所有 BeanDefinition 已登记,但还没实例化任何 Bean
BFPP 能做:
  - 占位符替换:PropertySourcesPlaceholderConfigurer 把 ${db.url} 换成实际值
  - @Configuration 处理:ConfigurationClassPostProcessor 解析 @Configuration/@Bean/@Import
  - 动态注册 BeanDefinition:可以在这里再注册新的 Bean 定义

一个重要子类是 BeanDefinitionRegistryPostProcessor(BFPP 的子接口),它能注册新的 BeanDefinition——很多框架(如 MyBatis 的 Mapper 扫描、Spring Boot 的自动配置)就是在这里动态往容器里塞 Bean 定义的。所以这一步是「Bean 定义最终确定的时刻」——过了这步,图纸就定型了,接下来该照图施工(造 Bean)。

四、第三阶段:注册 BeanPostProcessor(备好加工器)

第 6 步 registerBeanPostProcessors 注册(注意:只是注册,不是执行)所有 BeanPostProcessor(BPP)。BPP 是「Bean 实例的加工器」,要等第 11 步造 Bean 时才真正用到:

这一步:把所有 BPP 找出来、实例化、注册到 BeanFactory 里(备用)
为什么现在注册:因为第 11 步造 Bean 时,每个 Bean 的初始化前后都要调 BPP
             必须先把 BPP 准备好,造 Bean 时才有加工器可用

重要的 BPP:
  - AnnotationAwareAspectJAutoProxyCreator:AOP 代理(造 Bean 时生成代理)
  - AutowiredAnnotationBeanPostProcessor:处理 @Autowired 注入
  - CommonAnnotationBeanPostProcessor:处理 @Resource、@PostConstruct

顺序很关键:BPP 必须在「造 Bean(第 11 步)之前」注册好,否则造 Bean 时没有加工器,AOP、注入都无从谈起。这体现了 refresh 步骤顺序的严谨——每一步为后面的步骤做准备。

五、核心阶段:finishBeanFactoryInitialization 造 Bean

第 11 步 finishBeanFactoryInitializationrefresh 的核心——实例化所有非懒加载的单例 Bean。前面十步都是准备,这一步才真正「照图施工」:

遍历所有单例 BeanDefinition(非 lazy 的),对每个 Bean 走完整创建流程:
  ① 实例化(反射调构造器 new 对象)
  ② 属性填充(依赖注入,@Autowired 在这步生效,可能触发创建依赖的 Bean)
  ③ 初始化前:BeanPostProcessor.postProcessBeforeInitialization + @PostConstruct
  ④ 初始化:InitializingBean.afterPropertiesSet + initMethod
  ⑤ 初始化后:BeanPostProcessor.postProcessAfterInitialization(← AOP 代理在这生成!)
  ⑥ Bean 就绪,放入单例池(singletonObjects)

这一步和 Bean 生命周期、循环依赖、三级缓存都紧密相关——循环依赖的三级缓存就是在这一步的 Bean 创建过程中发挥作用的。「非懒加载单例在容器启动时就全部创建」也是在这一步体现的(所以启动慢往往是 Bean 太多、或某个 Bean 初始化耗时)。这是整个 refresh 里最重、最核心的一步。

六、收尾阶段与整体串联

最后第 12 步 finishRefresh 收尾:清理缓存、初始化生命周期处理器、发布 ContextRefreshedEvent 事件(通知「容器刷新完成」),Spring Boot 还会在相关钩子里触发 ApplicationReadyEvent。至此容器完全就绪。把整条流水线串起来:

准备环境 → 加载 BeanDefinition(读图纸)→ BFPP 改图纸 → 注册 BPP(备加工器)
→ 初始化事件多播器/监听器 → 【onRefresh 钩子:SpringBoot 启动内嵌 Tomcat】
→ 实例化所有单例 Bean(照图施工,AOP/注入/循环依赖都在这)→ 发布刷新完成事件

一条记忆主线:「读定义 → 改定义 → 备加工器 → 造 Bean → 就绪」。每个扩展点的执行时机都能对到这条线上——BFPP 在「改定义」、BPP 注册在「备加工器」、BPP 执行和 AOP 在「造 Bean」、事件监听在「就绪」。理解 refresh 的顺序,就理解了 Spring 所有机制「什么时候发生」。

记忆钩子:「refresh 是模板方法,固定顺序:准备→加载 BeanDefinition(读图纸,无实例)→ BFPP 改定义 → 注册 BPP(备加工器)→ 初始化事件多播器/监听器 → onRefresh 钩子(SpringBoot 启 Tomcat)→ finishBeanFactoryInitialization 造所有单例 Bean(AOP/注入/循环依赖在这)→ 发布 ContextRefreshedEvent 就绪」

七、常见误区与追问

  • 误区:容器启动时 Bean 就已经在加载 BeanDefinition 阶段被创建了。 加载 BeanDefinition 只是登记「图纸」,真正实例化 Bean 是在后面的 finishBeanFactoryInitialization(第 11 步)。
  • 误区:BeanPostProcessor 在注册那一步就执行了。 第 6 步只是注册(准备),BPP 的 before/after 方法要等第 11 步造 Bean 时、每个 Bean 初始化前后才执行。
  • 误区:所有 Bean 都在容器启动时创建。 只有非懒加载的单例在启动时创建;懒加载(@Lazy)Bean 和原型 Bean 在第一次使用时才创建。
  • 误区:refresh 只能调一次。 它叫「刷新」正是因为可以多次刷新(重新加载配置);每次刷新会销毁旧 Bean、重新走一遍流程。
  • 追问:BeanFactoryPostProcessor 和 BeanPostProcessor 在 refresh 里的执行顺序? BFPP 在第 5 步执行(改 BeanDefinition),BPP 在第 6 步注册、第 11 步造 Bean 时才执行——BFPP 整体早于 BPP 的执行,隔着「实例化」这条线。
  • 追问:Spring Boot 的内嵌 Tomcat 在 refresh 的哪一步启动?onRefresh() 钩子里——ServletWebServerApplicationContext 重写了 onRefresh,在这里创建并启动内嵌 Web 服务器。
  • 追问:循环依赖的三级缓存在 refresh 哪一步起作用? 在第 11 步 finishBeanFactoryInitialization 造 Bean 的过程中——创建 Bean 时若发现循环依赖,通过三级缓存提前暴露半成品 Bean 来打破循环。

八、加强记忆

Spring 容器启动的核心是 AbstractApplicationContext.refresh(),它是一个 synchronized 的模板方法,用固定顺序把「一堆配置」变成「一个可用容器」。记住这条主线:准备环境 → 加载 BeanDefinition(读图纸,只登记定义、无实例)→ 执行 BeanFactoryPostProcessor(改图纸,占位符替换、@Configuration 处理、动态注册 Bean 定义)→ 注册 BeanPostProcessor(备好 Bean 加工器,此时只注册不执行)→ 初始化事件多播器和监听器 → onRefresh 钩子(Spring Boot 在这启动内嵌 Tomcat)→ finishBeanFactoryInitialization(核心!实例化所有非懒加载单例 Bean,AOP 代理、@Autowired 注入、循环依赖三级缓存都在这一步)→ 发布 ContextRefreshedEvent 就绪。两个分水岭要记牢:第 2 步只有定义没有实例、第 11 步才真正造 BeanBFPP 改定义(实例化前)、BPP 注册在前但执行在造 Bean 时。每个扩展点的时机都能对到「读定义→改定义→备加工器→造 Bean→就绪」这条线上。一句话「refresh 模板方法:读图纸→改图纸→备加工器→造 Bean→就绪,Bean 在第 11 步才真正创建」。