Skip to content

介绍一下Spring的启动流程?

约 3188 字大约 11 分钟

Spring框架美团

2025-04-24

⭐ 题目日期:

美团 - 2025/04/12

📝 题解:

1. 概念解释

  • Spring Framework: 一个提供了全面的基础设施支持(如IoC、AOP、事务管理等)的Java企业级应用开发框架。它的核心是 IoC容器 (ApplicationContext),负责管理对象的生命周期和依赖关系(即Bean)。
  • Spring Boot: 它不是一个全新的框架,而是基于Spring Framework,旨在简化Spring应用的初始搭建以及开发过程。它通过“约定优于配置”的理念,提供了大量的自动配置 (Auto-Configuration)起步依赖 (Starters)内嵌Web服务器等特性,让开发者能快速启动和运行Spring应用。
  • Spring Boot启动流程: 指的是从执行main方法开始,到Spring Boot应用初始化完成,IoC容器准备就绪,(如果是Web应用)内嵌服务器启动并开始监听端口,最终应用能够对外提供服务的整个过程。

通俗比喻: 想象一下开一家餐厅(你的应用程序):

  • Spring Framework: 提供了厨房的各种基础设备和规范(灶台、冰箱、食材处理流程 - IoC, AOP等)。你需要自己挑选设备、组装、连接水电煤。
  • Spring Boot: 提供了一个“中央厨房”解决方案。你只需要说你要开一家西餐厅(引入spring-boot-starter-web),它就自动帮你配齐了烤箱(内嵌Tomcat)、标准化的厨具和菜单(自动配置),甚至基础调料都备好了。你只需要专注于烹饪你的特色菜(业务逻辑)。
  • 启动流程: 就是从餐厅设计图(main方法和@SpringBootApplication)开始,到采购设备(加载配置)、组装厨房(创建ApplicationContext)、招聘厨师和服务员(实例化Beans)、调试设备(Bean初始化和后置处理)、开门迎客(启动内嵌服务器)的整个过程。

2. 解题思路 (核心流程)

Spring Boot的启动核心在于 SpringApplication.run() 方法。我们可以将整个流程概括为以下几个关键步骤:

  1. 创建 SpringApplication 实例:

    • 当你调用 SpringApplication.run(MyApplication.class, args) 时,首先会创建一个 SpringApplication 对象。
    • 在这个过程中,它会进行一些初始化工作,比如:
      • 判断应用类型(是否是Web应用)。
      • 加载 META-INF/spring.factories 文件中配置的 ApplicationContextInitializerApplicationListener。这些是Spring Boot进行扩展和定制的关键点。
  2. 准备环境 (Environment):

    • 创建并配置应用的 Environment 对象。
    • Environment 包含了应用的配置信息来源,如:命令行参数 (args)、JVM系统属性、操作系统环境变量、application.propertiesapplication.yml 文件、@PropertySource 指定的配置文件等。
    • 还会处理激活的 Profiles (例如 dev, test, prod),不同Profile下的配置会覆盖默认配置。
  3. 打印 Banner:

    • 在控制台输出 Spring Boot 的启动图标(Banner),可以通过在 src/main/resources 下放置 banner.txt 或图片来自定义。
  4. 创建 ApplicationContext (IoC容器):

    • 根据之前判断的应用类型(Web/非Web),创建对应的 ApplicationContext 实例。
      • Web应用 (Servlet): AnnotationConfigServletWebServerApplicationContext
      • Web应用 (Reactive): AnnotationConfigReactiveWebServerApplicationContext
      • 非Web应用: AnnotationConfigApplicationContext
    • 这个 ApplicationContext 就是Spring的核心容器,负责管理所有的Bean。
  5. 预处理 ApplicationContext:

    • refresh() 之前,调用之前加载的 ApplicationContextInitializerinitialize 方法,对 ApplicationContext 进行进一步的配置和定制化。
    • 加载所有找到的 Bean 定义(扫描 @Component, @Service, @Repository, @Controller, @Configuration 等注解的类),但此时Bean还没有被实例化
  6. refresh() ApplicationContext (⭐ 核心步骤):

    • 这是 Spring 容器初始化的核心,也是最复杂的一步。refresh() 方法内部执行了一系列操作来启动容器,主要包括:
      • prepareRefresh(): 准备工作,如设置启动时间,设置激活状态等。
      • obtainFreshBeanFactory(): 创建或获取内部的 BeanFactory
      • prepareBeanFactory(beanFactory): 配置 BeanFactory,例如设置类加载器,添加 BeanPostProcessor (Bean后置处理器,非常重要!)。
      • postProcessBeanFactory(beanFactory): 子类覆盖的方法,允许在 BeanFactory 标准初始化后进行特定的后置处理(例如 ServletWebServerApplicationContext 在这里进行 Servlet 相关处理)。
      • invokeBeanFactoryPostProcessors(beanFactory): (关键点) 执行所有注册的 BeanFactoryPostProcessor。这是 Spring Boot 自动配置的核心所在。@EnableAutoConfiguration 会导入 AutoConfigurationImportSelector,它会扫描 META-INF/spring.factories 中定义的自动配置类 (EnableAutoConfiguration key 下的类)。这些自动配置类通常带有 @ConditionalOn... 注解,只有满足条件时才会生效,它们内部定义了大量的Bean。ConfigurationClassPostProcessor 是一个非常重要的 BeanFactoryPostProcessor,负责解析处理 @Configuration 类、@Bean 方法、@Import@ComponentScan 等。
      • registerBeanPostProcessors(beanFactory): 注册所有 BeanPostProcessor。注意,BeanPostProcessorBeanFactoryPostProcessor 不同,前者是在 Bean 实例化之后(初始化前后)执行,后者是在所有 Bean 定义加载完成之后,Bean 实例化之前执行。
      • initMessageSource(): 初始化国际化消息源。
      • initApplicationEventMulticaster(): 初始化应用事件广播器。
      • onRefresh(): 子类覆盖的方法,用于特定上下文的刷新工作。对于Web应用,内嵌Web服务器(如Tomcat)就是在这个阶段创建和启动的
      • registerListeners(): 注册 ApplicationListener 到事件广播器。
      • finishBeanFactoryInitialization(beanFactory): (关键点) 实例化所有非懒加载的单例 Bean。这是依赖注入(DI)发生的地方。Spring会按照依赖关系依次创建Bean,并调用 BeanPostProcessor 进行后置处理(例如处理 @Autowired, @PostConstruct 等注解)。
      • finishRefresh(): 完成刷新过程,发布 ContextRefreshedEvent 事件,通知容器已就绪。
  7. afterRefresh() 后置处理:

    • refresh() 完成后,执行一些收尾工作。
  8. 发布 ApplicationReadyEvent:

    • 表示应用已经准备好接收请求(对于Web应用,此时内嵌服务器已启动并监听端口)。
  9. 调用 ApplicationRunnerCommandLineRunner:

    • 执行所有实现了 ApplicationRunnerCommandLineRunner 接口的 Bean 的 run 方法。这通常用于在应用启动后执行一些初始化任务,如加载数据、启动后台任务等。

流程图:

3. 知识扩展

  • @SpringBootApplication注解: 这通常是启动类的核心注解,它是一个组合注解,包含了:
    • @SpringBootConfiguration: 继承自@Configuration,表明当前类是配置类。
    • @EnableAutoConfiguration: 启用Spring Boot的自动配置机制。核心是导入 AutoConfigurationImportSelector
    • @ComponentScan: 自动扫描启动类所在包及其子包下的组件(@Component, @Service, etc.)。
  • 自动配置 (Auto-Configuration):
    • 原理: 利用 META-INF/spring.factories 文件 + @ConditionalOn... 注解。Spring Boot启动时会扫描所有jar包中的 spring.factories 文件,找到 org.springframework.boot.autoconfigure.EnableAutoConfiguration key 下的配置类列表。
    • @ConditionalOn...: 这些注解(如 @ConditionalOnClass, @ConditionalOnBean, @ConditionalOnProperty)使得自动配置类只有在满足特定条件时才会生效,从而避免了不必要的Bean创建。例如,只有当classpath下存在 Servlet.classTomcat.class 时,ServletWebServerFactoryAutoConfiguration 才会尝试配置内嵌Tomcat。
  • 起步依赖 (Starters):
    • spring-boot-starter-web, spring-boot-starter-data-jpa 等。它们本身不包含代码,主要作用是管理依赖。引入一个starter会将相关的库(包括传递依赖)一起添加到项目中,简化了依赖配置。例如,spring-boot-starter-web 会引入Spring MVC、Tomcat(默认)、Jackson等Web开发所需的核心库。
  • Bean的生命周期: 启动流程与Bean的生命周期紧密相关。refresh() 过程中的 finishBeanFactoryInitialization 阶段就涉及了Bean的实例化、属性填充(依赖注入)、初始化(调用 InitializingBeanafterPropertiesSet@PostConstruct 方法)、以及通过 BeanPostProcessor 进行的各种增强。
  • 事件监听机制: Spring Boot在启动过程中会发布多个事件(如 ApplicationStartingEvent, ApplicationEnvironmentPreparedEvent, ApplicationPreparedEvent, ContextRefreshedEvent, ApplicationReadyEvent, ApplicationFailedEvent),开发者可以实现 ApplicationListener 来监听这些事件,并在特定阶段执行自定义逻辑。

4. 实际应用

理解启动流程对于日常开发和问题排查至关重要:

  • 问题排查:
    • 启动缓慢: 分析 refresh() 过程中哪个环节耗时较长,是Bean实例化复杂、依赖外部资源慢,还是 BeanFactoryPostProcessorBeanPostProcessor 逻辑过多?可以通过开启DEBUG日志 (logging.level.org.springframework=DEBUG) 或使用 Spring Boot Actuator 的 /startup 端点(需要配置 spring-boot-starter-actuator 并开启)来分析。
    • Bean冲突/未找到 (NoSuchBeanDefinitionException, NoUniqueBeanDefinitionException): 通常发生在 invokeBeanFactoryPostProcessorsfinishBeanFactoryInitialization 阶段。检查 @ComponentScan 范围是否正确、自动配置条件是否满足、是否有重复定义。
    • 配置未生效: 检查 Environment 加载配置的优先级,确认 application.properties/yml 是否被正确加载,Profile 是否激活正确。
    • 端口占用: 发生在 onRefresh() 启动内嵌服务器时。
  • 定制化开发:
    • ApplicationContextInitializer: 在 refresh() 之前对 ApplicationContext 进行编程方式的配置,例如动态注册属性源。
    • BeanFactoryPostProcessor: 在Bean实例化前修改Bean定义,例如修改Bean的作用域、添加额外的属性。
    • BeanPostProcessor: 在Bean实例化后、初始化前后进行干预,例如实现自定义注解的处理、动态代理等(AOP的底层实现就依赖它)。
    • ApplicationRunner/CommandLineRunner: 在应用完全启动后执行一次性任务,如数据初始化、缓存预热、启动定时任务调度器等。
    • 自定义 spring.factories: 实现自定义的自动配置或监听器。

案例分析: 假设你需要在一个服务启动后,立即从配置中心拉取最新的动态配置并应用。你可以在实现了 ApplicationListener<ApplicationReadyEvent> 的Bean中执行这个逻辑,确保此时应用环境(包括网络连接等)已经准备就绪。或者,如果需要在Bean实例化之前就基于某些配置动态调整Bean定义,可以考虑实现 BeanFactoryPostProcessor

5. 常见陷阱

面试时回答此问题容易犯的错误:

  1. 混淆 Spring Framework 和 Spring Boot: 只讲 Spring Framework 的 ApplicationContext 创建,忽略了 Spring Boot 的 SpringApplication、自动配置、内嵌服务器等关键特性。
  2. 流程不清晰/跳跃: 无法按顺序描述关键步骤,特别是 refresh() 内部的主要阶段。
  3. 对核心概念理解模糊:ApplicationContext, BeanFactory, BeanFactoryPostProcessor, BeanPostProcessor 的作用和执行时机分不清楚。
  4. 不了解自动配置原理: 无法解释 @EnableAutoConfigurationspring.factories 是如何工作的,以及 @ConditionalOn... 的作用。
  5. 缺乏与实际应用的结合: 只是背诵流程,无法说明理解这个流程对解决实际问题(如调试、定制)有什么帮助。
  6. 忽略Web服务器启动: 对于Web应用,忘记提及内嵌服务器(如Tomcat)是在 onRefresh() 阶段启动的。
  7. ApplicationRunner/CommandLineRunner 不熟悉: 不知道它们的作用和执行时机。

如何避免:

  • 突出 Spring Boot 特色: 明确提到 SpringApplication, 自动配置, Starters, 内嵌服务器。
  • 抓住主线:SpringApplication.run() 为起点,refresh() 为核心,按顺序讲解。
  • 理解核心接口: 弄懂 BeanFactoryPostProcessor (处理定义) 和 BeanPostProcessor (处理实例) 的区别和时机。
  • 掌握自动配置: 解释 spring.factories@Conditional
  • 联系实际: 举例说明启动流程知识如何用于排错和扩展。
  • 结构化表达: 使用清晰的步骤或图表辅助说明。

总结: 回答这个问题时,要展现出对 Spring Boot 整体架构的理解,从宏观的 SpringApplication.run() 到微观的 refresh() 内部关键步骤,再到自动配置、Bean生命周期等核心机制,最后能结合实际应用场景,体现出解决问题的能力。对于校招生,能清晰描述主要流程并解释清楚自动配置原理,就已经是一个很好的答案了。如果能进一步深入到 refresh() 的关键子步骤和相关接口的作用,会是重要的加分项。