SpringBoot启动源码深度解析:从自动装配到内嵌服务器启动全流程

📅 2026/8/14 10:19:14
SpringBoot启动源码深度解析:从自动装配到内嵌服务器启动全流程
1. 项目概述为什么我们需要深入SpringBoot启动源码如果你是一个Java开发者尤其是使用SpringBoot框架的开发者你可能已经习惯了在main方法里写上一行SpringApplication.run(YourApplication.class, args)然后一个功能完备的Web应用就启动了。这太方便了方便到我们几乎不再关心背后发生了什么。但当你遇到启动失败、配置不生效、Bean加载顺序诡异、或者想深度定制启动流程时这种“黑盒”状态就会让你束手无策。我经历过很多次这样的时刻一个看似简单的依赖冲突导致应用启动卡住半小时一个自定义的BeanPostProcessor没有按照预期执行想实现一个类似Conditional的注解却无从下手。最终解决问题的钥匙都藏在源码里。因此我决定花时间把SpringBoot从点击“运行”到服务就绪的整个启动流程像解剖麻雀一样彻底拆解一遍。这不是一篇简单的API罗列而是结合我踩过的坑和调试经验带你走一遍SpringBoot的“心路历程”。无论你是想应对高级面试还是想真正掌握框架以进行深度定制这篇超详细的源码解析都将为你提供一张清晰的“地图”。2. 启动流程全景与核心脉络拆解在深入代码之前我们需要建立一个宏观的认知。SpringBoot的启动过程本质上是一个事件驱动、生命周期明确、高度可扩展的初始化流程。它并非一蹴而就而是分阶段、分层次地将一个简单的Java类逐步膨胀为一个完整的Spring应用上下文ApplicationContext并最终启动内嵌的Web服务器。整个流程可以概括为以下几个核心阶段这也是我们后续解析的路线图初始化阶段创建SpringApplication实例推断应用类型加载初始化器和监听器。运行阶段执行SpringApplication.run()方法这是整个流程的引擎。准备环境阶段创建并配置应用运行环境Environment加载配置文件如application.yml。创建应用上下文阶段根据应用类型Servlet、Reactive等实例化对应的ApplicationContext。刷新应用上下文阶段这是Spring框架的核心包括Bean定义加载、Bean工厂后处理、Bean实例化、依赖注入、AOP代理等。后置处理与服务器启动阶段执行刷新后的回调启动内嵌Web服务器如Tomcat并发布应用启动完成事件。理解这个脉络后我们就能带着问题去看源码每个阶段具体做了什么提供了哪些扩展点常见的坑都出在哪里2.1 核心类SpringApplication的初始化一切始于SpringApplication的构造方法。当我们调用new SpringApplication(primarySources)或使用SpringApplication.run(YourApplication.class, args)的静态方法时构造过程就开始了。// 简化后的核心初始化逻辑 public SpringApplication(ResourceLoader resourceLoader, Class?... primarySources) { this.resourceLoader resourceLoader; this.primarySources new LinkedHashSet(Arrays.asList(primarySources)); // 1. 推断Web应用类型 this.webApplicationType WebApplicationType.deduceFromClasspath(); // 2. 加载并设置“应用上下文初始化器” setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 3. 加载并设置“应用监听器” setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 4. 推断主应用类 this.mainApplicationClass deduceMainApplicationClass(); }关键点解析与实操心得Web应用类型推断WebApplicationType.deduceFromClasspath()方法通过检查类路径下是否存在特定的类来判断应用类型。例如存在Servlet和ConfigurableWebApplicationContext类但不存在WebFlux相关类则推断为SERVLET类型即传统的Spring MVC应用。这个推断决定了后续创建哪种ApplicationContext。踩坑提示如果你在非Web项目中错误引入了spring-boot-starter-web依赖它会被推断为Web应用可能导致不必要的资源消耗和端口占用。getSpringFactoriesInstances机制这是SpringBoot“约定优于配置”和自动装配的灵魂机制之一。它会从所有jar包的META-INF/spring.factories文件中读取指定接口如ApplicationContextInitializer.class的全限定类名然后实例化。我们自定义的初始化器或监听器也是通过在这个文件中配置来生效的。实操技巧在SpringBoot 2.7版本更推荐使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件来定义自动配置类但spring.factories对于初始化器和监听器依然有效。2.2run方法启动引擎的详细拆解SpringApplication.run()方法是启动流程的总入口。它返回一个ConfigurableApplicationContext但内部过程极其丰富。public ConfigurableApplicationContext run(String... args) { // 1. 创建并启动“停止监视器”用于优雅关机 StopWatch stopWatch new StopWatch(); stopWatch.start(); // 2. 初始化一个空的“引导上下文”主要用于加载外部配置如Spring Cloud场景 ConfigurableApplicationContext context null; CollectionSpringBootExceptionReporter exceptionReporters new ArrayList(); configureHeadlessProperty(); // 3. 获取并启动“运行时监听器” SpringApplicationRunListeners listeners getRunListeners(args); listeners.starting(); // 发布“应用开始启动”事件 try { // 4. 准备应用参数和环境 ApplicationArguments applicationArguments new DefaultApplicationArguments(args); ConfigurableEnvironment environment prepareEnvironment(listeners, applicationArguments); // 处理需要忽略的Bean信息 configureIgnoreBeanInfo(environment); // 5. 打印Banner Banner printedBanner printBanner(environment); // 6. 创建应用上下文 context createApplicationContext(); // 获取异常报告器 exceptionReporters getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 7. 准备上下文关联环境、设置BeanName生成器、资源加载器、应用启动器并执行初始化器 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 8. 刷新上下文最核心、最复杂的步骤 refreshContext(context); // 9. 刷新后的后置处理默认为空方法用于扩展 afterRefresh(context, applicationArguments); // 停止计时 stopWatch.stop(); // 10. 发布“应用已启动”事件 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); // 发布“上下文已刷新”事件 // 11. 执行ApplicationRunner和CommandLineRunner callRunners(context, applicationArguments); // 12. 发布“应用就绪”事件 listeners.running(context); } catch (Throwable ex) { // 异常处理... } return context; }这个流程清晰地展示了SpringBoot如何通过事件监听机制SpringApplicationRunListeners将启动过程模块化、可观测化。开发者可以通过实现ApplicationListener接口并监听特定事件如ApplicationStartingEvent,ApplicationPreparedEvent等在生命周期的特定节点插入自定义逻辑。3. 核心阶段深度解析与实操要点3.1 环境准备prepareEnvironment环境是应用的基石包含了配置文件、JVM系统属性、操作系统环境变量等。prepareEnvironment方法负责创建和配置它。private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // 1. 根据Web应用类型创建对应的环境对象StandardServletEnvironment或StandardEnvironment等 ConfigurableEnvironment environment getOrCreateEnvironment(); // 2. 配置环境主要是处理命令行参数--server.port8080这种 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 3. 将环境绑定到SpringApplication本身通过ConfigurationPropertySources ConfigurationPropertySources.attach(environment); // 4. 通知所有监听器环境已准备就绪。这是关键扩展点 listeners.environmentPrepared(environment); // 5. 将环境移动到当前上下文 bindToSpringApplication(environment); // 6. 如果不是自定义环境进行额外配置如转换非字符串属性 if (!this.isCustomEnvironment) { environment new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // 7. 再次附加配置属性源确保顺序 ConfigurationPropertySources.attach(environment); return environment; }注意事项与排查技巧配置加载顺序SpringBoot的属性源有严格的优先级。listeners.environmentPrepared(environment)这一步会触发ConfigFileApplicationListener它负责从application.properties,application.yml,application-{profile}.yml等文件加载配置。其优先级顺序是命令行参数 Java系统属性 操作系统环境变量 当前profile的配置文件 默认配置文件。理解这个顺序对排查配置冲突至关重要。环境未绑定错误如果你在自定义的ApplicationContextInitializer中过早地尝试从Environment获取属性可能会失败因为此时环境可能还未完全绑定到SpringApplication。正确的做法是在environmentPrepared事件之后或ApplicationContext创建之后再操作。自定义环境如果你想完全控制环境的创建例如集成Apollo、Nacos等配置中心可以重写SpringApplication的getOrCreateEnvironment()方法返回你自己的ConfigurableEnvironment实现。3.2 创建应用上下文createApplicationContext根据之前推断的webApplicationTypeSpringBoot会实例化对应的ApplicationContext。protected ConfigurableApplicationContext createApplicationContext() { Class? contextClass this.applicationContextClass; if (contextClass null) { try { // 根据应用类型选择上下文类 switch (this.webApplicationType) { case SERVLET: contextClass Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); // AnnotationConfigServletWebServerApplicationContext break; case REACTIVE: contextClass Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); // AnnotationConfigReactiveWebServerApplicationContext break; default: contextClass Class.forName(DEFAULT_CONTEXT_CLASS); // AnnotationConfigApplicationContext } } catch (ClassNotFoundException ex) { // ... } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }核心要点对于最常见的Servlet Web应用创建的是AnnotationConfigServletWebServerApplicationContext。这个类非常重要它集成了注解配置的能力AnnotationConfig、Servlet Web支持以及内嵌Web服务器WebServer的生命周期管理。它是我们后续分析refresh()方法的基础。3.3 准备上下文prepareContext在刷新refresh之前需要对新创建的ApplicationContext进行一番“梳妆打扮”。private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 1. 将环境设置到上下文中 context.setEnvironment(environment); // 2. 后置处理上下文设置资源加载器、类型转换器、应用启动器 postProcessApplicationContext(context); // 3. 执行所有“应用上下文初始化器” applyInitializers(context); // 4. 发布“上下文已准备”事件此时上下文和环境已关联但Bean尚未加载 listeners.contextPrepared(context); // 5. 打印启动日志 if (this.logStartupInfo) { logStartupInfo(context.getParent() null); logStartupProfileInfo(context); } // 6. 获取BeanFactory并注册一些特殊的单例Bean ConfigurableListableBeanFactory beanFactory context.getBeanFactory(); beanFactory.registerSingleton(springApplicationArguments, applicationArguments); if (printedBanner ! null) { beanFactory.registerSingleton(springBootBanner, printedBanner); } // 7. 设置是否允许Bean定义覆盖 if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // 8. 延迟加载如果设置了将主配置类SpringBootApplication标注的类注册为Bean定义 if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // 9. 加载主配置类即我们的启动类及其导入的配置 SetObject sources getAllSources(); load(context, sources.toArray(new Object[0])); // 10. 发布“上下文已加载”事件此时Bean定义已加载但Bean尚未实例化 listeners.contextLoaded(context); }关键步骤深度解析applyInitializers(context)这里会遍历执行在初始化阶段加载的所有ApplicationContextInitializer。这是一个非常重要的扩展点允许我们在ApplicationContext刷新之前对其做一些自定义配置。例如你可以在这里动态注册Bean定义、修改环境属性、添加BeanFactoryPostProcessor等。load(context, sources...)这个方法负责将我们的主启动类primarySources加载到BeanDefinitionRegistry中。对于注解配置的上下文它内部会创建一个AnnotatedBeanDefinitionReader将主类注册为一个Bean定义。主类上的SpringBootApplication注解它本身是一个组合注解包含SpringBootConfiguration,EnableAutoConfiguration,ComponentScan的元数据在这里被读取但真正的扫描和自动装配逻辑是在后续的refresh()中触发的。实操心得ApplicationContextInitializer的执行时机非常早在Bean定义加载之前。这使它成为影响自动装配和Bean加载流程的绝佳位置。比如你可以用它来动态激活某个Profile或者向BeanFactory注册一个自定义的BeanDefinitionRegistryPostProcessor从而干预Bean定义的扫描和注册过程。4. 核心中的核心应用上下文刷新refreshContextrefreshContext最终会调用AbstractApplicationContext.refresh()方法。这是Spring框架最核心、最复杂的方法SpringBoot的自动装配、内嵌服务器启动等魔法都发生在这里。我们结合SpringBoot的特定实现ServletWebServerApplicationContext来解析。// 这是Spring框架AbstractApplicationContext中的方法SpringBoot的上下文重写了其中部分步骤 public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备刷新设置启动时间、激活状态初始化属性源空方法子类可扩展 prepareRefresh(); // 2. 获取新的BeanFactory通常刷新意味着销毁旧的创建新的 ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory(); // 3. 准备BeanFactory配置BeanFactory的标准特性类加载器、表达式解析器等 prepareBeanFactory(beanFactory); try { // 4. 后置处理BeanFactory允许子类在Bean定义加载后实例化前对其进行修改 postProcessBeanFactory(beanFactory); // 5. 调用BeanFactory的后置处理器这是关键 invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册Bean的后置处理器用于拦截Bean的创建过程 registerBeanPostProcessors(beanFactory); // 7. 初始化消息源国际化 initMessageSource(); // 8. 初始化事件广播器 initApplicationEventMulticaster(); // 9. 初始化其他特殊的Bean由子类实现SpringBoot在这里启动Web服务器 onRefresh(); // 10. 注册监听器 registerListeners(); // 11. 实例化所有剩余的单例Bean非懒加载的 finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新发布上下文刷新完成事件 finishRefresh(); } catch (BeansException ex) { // ... 异常处理销毁已创建的Bean ... throw ex; } finally { // 13. 重置Spring核心中的公共缓存 resetCommonCaches(); } } }对于SpringBoot的ServletWebServerApplicationContext它重写了第4步postProcessBeanFactory、第9步onRefresh和第12步finishRefresh。我们重点关注与Boot特性紧密相关的第5步和第9步。4.1 自动装配的引擎invokeBeanFactoryPostProcessors这一步是SpringBoot自动装配原理的核心执行阶段。它会调用所有BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor。首先处理BeanDefinitionRegistryPostProcessor这类处理器可以注册新的Bean定义。其中最关键的是ConfigurationClassPostProcessor它负责处理Configuration注解的类。ConfigurationClassPostProcessor的工作它会找到所有标注了Configuration的Bean我们的主启动类就是其中之一。解析该类上的注解特别是ComponentScan和Import。ComponentScan根据指定的包路径扫描Component,Service,Repository,Controller等注解的类并将它们注册为Bean定义。Import导入其他配置类。EnableAutoConfiguration本质上就是一个Import(AutoConfigurationImportSelector.class)。AutoConfigurationImportSelector的魔法这个类是自动装配的“大脑”。它的selectImports方法会从META-INF/spring.factories或spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中读取org.springframework.boot.autoconfigure.EnableAutoConfiguration键对应的所有自动配置类全名。通过一系列Conditional注解如ConditionalOnClass,ConditionalOnBean,ConditionalOnProperty对这些类进行过滤只将符合条件的配置类导入到当前上下文中。这些自动配置类例如DataSourceAutoConfiguration,WebMvcAutoConfiguration内部使用Bean方法定义了一系列预设好的Bean。这就是为什么我们引入一个starter依赖相关功能就自动可用的原因。排查技巧如果你的自动配置没有生效可以在这里入手调试。在ConfigurationClassPostProcessor和AutoConfigurationImportSelector的相关方法上打断点查看哪些配置类被扫描到、哪些被排除了排除条件是什么。4.2 启动内嵌Web服务器onRefreshServletWebServerApplicationContext重写了onRefresh()方法。protected void onRefresh() { super.onRefresh(); // 调用父类逻辑 try { createWebServer(); // 创建Web服务器 } catch (Throwable ex) { // ... } } private void createWebServer() { WebServer webServer this.webServer; ServletContext servletContext getServletContext(); if (webServer null servletContext null) { // 1. 从BeanFactory中获取WebServerFactory通常是自动配置的TomcatServletWebServerFactory ServletWebServerFactory factory getWebServerFactory(); // 2. 使用工厂创建WebServer同时会初始化ServletContext并注册DispatcherServlet this.webServer factory.getWebServer(getSelfInitializer()); } else if (servletContext ! null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { // ... } } initPropertySources(); // 初始化属性源 }关键过程getWebServerFactory()会从Spring容器中查找ServletWebServerFactory类型的Bean。TomcatServletWebServerFactory就是由ServletWebServerFactoryAutoConfiguration在满足条件时自动配置的。factory.getWebServer(getSelfInitializer())是创建服务器的核心。它会实例化Tomcat/Jetty/Undertow等服务器对象。创建ServletContext。调用传入的ServletContextInitializer由getSelfInitializer()返回它负责注册DispatcherServlet、Filters等。应用我们在application.properties中配置的服务器属性端口、上下文路径、SSL等。注意事项此时Web服务器对象虽然创建了但端口还未监听。真正的端口绑定和服务器启动是在最后的finishRefresh()阶段通过发布ContextRefreshedEvent事件触发WebServerStartStopLifecycle这个Lifecycle接口的实现来完成的。这种设计将服务器的创建和启动分离提供了更大的灵活性。4.3 完成Bean初始化finishBeanFactoryInitialization这一步会实例化所有非懒加载的单例Bean。BeanFactory会遍历所有Bean定义通过反射或工厂方法创建Bean实例处理依赖注入Autowired,Resource应用BeanPostProcessor如进行AOP代理然后调用初始化方法PostConstruct,InitializingBean。常见问题定位Bean创建失败如果在此阶段抛出BeanCreationException通常是因为依赖注入失败找不到依赖的Bean、Bean的初始化方法抛出异常、或BeanPostProcessor处理出错。需要仔细查看异常堆栈定位到具体的Bean和原因。循环依赖Spring通过三级缓存机制解决了Setter注入和字段注入的循环依赖问题。但如果使用构造器注入且形成循环Spring无法解决会抛出BeanCurrentlyInCreationException。这是设计问题需要重构代码打破循环。5. 启动后流程与扩展点5.1 执行RunnercallRunners在refresh()完成且listeners.started(context)事件发布后SpringBoot会执行所有ApplicationRunner和CommandLineRunner类型的Bean。这两个接口都提供一个run方法用于在应用完全启动后、开始接受请求前执行一些特定的初始化任务。private void callRunners(ApplicationContext context, ApplicationArguments args) { ListObject runners new ArrayList(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }使用场景与区别ApplicationRunner的run方法接收的是封装好的ApplicationArguments对象可以方便地获取解析后的命令行参数如--keyvalue。CommandLineRunner的run方法接收的是原始的字符串数组String... args。两者都支持Order注解来定义执行顺序。一个常见的坑如果Runner中的任务耗时很长会阻塞应用的启动完成导致健康检查失败。对于异步任务应考虑在Runner中启动一个异步线程或使用EventListener监听ApplicationReadyEvent事件来执行。5.2 发布就绪事件listeners.running(context)最后SpringBoot会发布ApplicationReadyEvent。这个事件标志着应用已完全启动内嵌服务器已开始监听端口可以对外提供服务了。监听这个事件是执行启动后逻辑最安全的位置。6. 常见问题排查与调试技巧实录结合源码我们可以系统化地定位启动期问题。6.1 Bean定义加载失败症状启动时报BeanDefinitionStoreException或ConfigurationClassParseException。排查检查主配置类路径是否正确是否被组件扫描到。检查Import、ComponentScan注解的使用是否有误导致循环引用或扫描了不希望的包。在ConfigurationClassPostProcessor的processConfigBeanDefinitions方法处打断点查看正在处理的配置类列表。6.2 自动配置未生效症状引入了starter但相关的Bean没有创建。排查检查依赖是否真的引入成功。在AutoConfigurationImportSelector的getCandidateConfigurations和filter方法处打断点查看候选配置类有哪些以及被过滤掉的原因通常是Conditional条件不满足。开启调试日志在application.properties中添加debugtrue。启动时SpringBoot会打印一份详细的自动配置报告显示哪些配置类生效Positive matches哪些未生效及原因Negative matches。这是最实用的排查工具。6.3 端口被占用或服务器启动失败症状WebServer启动失败报端口绑定异常或Servlet初始化错误。排查检查ServletWebServerFactoryBean是否成功创建。可以在ServletWebServerFactoryAutoConfiguration类上打断点。检查onRefresh()中的createWebServer()方法看WebServer对象是否成功创建。检查WebServerStartStopLifecycle的start()方法是否被调用。服务器是在finishRefresh()发布事件后异步启动的。6.4 启动速度慢症状应用启动时间过长。分析与优化组件扫描路径过大ComponentScan如果指定了过大的包范围如根包com会扫描很多不必要的类。应精确指定到业务模块包。过多的自动配置类不是所有starter的自动配置都是需要的。可以通过spring.autoconfigure.exclude属性排除不必要的自动配置。懒加载SpringBoot 2.2支持全局懒加载(spring.main.lazy-initializationtrue)或者使用Lazy注解。但这可能会将启动时的问题延迟到第一次请求时。使用Spring Boot DevTools它在开发时通过重启类加载器来加速重启但对冷启动帮助不大。分析工具使用SpringApplication的setBannerMode(Banner.Mode.OFF)关闭Banner或使用-verbose:classJVM参数查看类加载情况。更专业的可以使用AsyncProfiler或JFR分析启动热点。理解SpringBoot的启动源码就像掌握了应用的“生命图谱”。当问题出现时你不再是在黑暗中摸索而是可以沿着这张图谱快速定位到问题发生的具体阶段和组件。从环境准备、Bean定义加载、自动装配条件匹配到Bean实例化、服务器启动每一个环节都有其明确的职责和扩展点。这份理解不仅能帮你高效解决问题更能让你在架构设计和框架定制时游刃有余。