
1. 从一行命令到整个应用SpringBoot启动的宏观视角“java -jar your-app.jar”这行简单的命令背后SpringBoot究竟为我们做了什么作为一名常年与SpringBoot打交道的开发者我见过太多人只停留在“自动配置真方便”的层面一旦遇到启动卡住、Bean加载失败、配置不生效等问题排查起来就一头雾水。今天我们就深入SpringBoot的腹地从源码层面完整地走一遍它的启动流程。这不是一次简单的API罗列而是带你像调试自己的代码一样理解SpringBoot如何从一个静态的main方法一步步构建起一个动态的、可运行的Web应用容器。理解这个过程不仅能让你在面试时游刃有余更重要的是当你的应用在凌晨三点启动失败时你能清晰地知道该从哪里打断点。整个启动流程可以粗略地分为几个核心阶段初始化启动类、准备运行时环境、创建并刷新应用上下文、执行Runner。我们接下来将深入每个阶段看看SpringBoot源码中那些精妙的设计与关键的抉择点。2. 一切的起点SpringApplication的构造与初始化启动的入口无疑是SpringApplication.run(YourApplication.class, args)。我们首先进入SpringApplication的构造函数。public SpringApplication(ResourceLoader resourceLoader, Class?... primarySources) { this.resourceLoader resourceLoader; this.primarySources new LinkedHashSet(Arrays.asList(primarySources)); this.webApplicationType WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers new ArrayList( getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass deduceMainApplicationClass(); }这里有几个关键动作决定了后续所有行为的基础。2.1 推断Web应用类型WebApplicationType.deduceFromClasspath()这是SpringBoot“智能”的起点。它通过检查当前类路径下是否存在特定的类来判断你究竟要运行一个什么类型的应用SERVLET类路径下存在javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext。这对应传统的Spring MVC Web应用。REACTIVE类路径下存在org.springframework.web.reactive.DispatcherHandler且不存在Servlet相关类。这对应Spring WebFlux响应式Web应用。NONE以上两者都不满足。这对应一个普通的、非Web的后台应用或批处理任务。这个推断结果至关重要它直接决定了后续要创建的ApplicationContext的类型是AnnotationConfigServletWebServerApplicationContext还是AnnotationConfigReactiveWebServerApplicationContext亦或是AnnotationConfigApplicationContext以及是否要内嵌一个Web服务器Tomcat、Jetty等。实操心得有时候你的非Web应用莫名其妙尝试启动Tomcat很可能就是依赖里不小心引入了spring-boot-starter-web。反过来一个Web应用启动后没有Web服务器也要检查这个推断是否因为依赖冲突或类加载问题而出错。你可以通过SpringApplication.setWebApplicationType(WebApplicationType)在启动前手动指定来覆盖自动推断。2.2 加载工厂类getSpringFactoriesInstances的秘密setInitializers和setListeners这两行代码是SpringBoot“约定大于配置”和扩展能力的核心实现。它们都调用了getSpringFactoriesInstances方法。这个方法会从所有jar包的META-INF/spring.factories文件中读取指定接口如ApplicationContextInitializer的全限定类名然后实例化它们。这是SpringBoot SPIService Provider Interface机制的关键。ApplicationContextInitializer在ApplicationContext刷新refresh之前对其进行编程式初始化的回调接口。比如SharedMetadataReaderFactoryContextInitializer就用于提前初始化元数据读取器提升后续扫描性能。ApplicationListener监听Spring应用事件如ApplicationStartedEvent、ApplicationFailedEvent的监听器。很多自动配置和内部功能都通过监听器来触发。SpringBoot自身的spring-boot-autoconfigure包下的spring.factories文件里就定义了一大堆这样的初始化和监听器。这也是为什么你引入一个Starter相关功能就能自动生效的原因之一——相关的配置类、初始化器、监听器都在这里声明了。踩坑记录如果你自己实现了一个ApplicationContextInitializer并打包成jar给其他项目用务必记得在你项目的META-INF/spring.factories里加上org.springframework.context.ApplicationContextInitializercom.your.initializer。我曾经花了半天时间排查为什么自定义的初始化器不生效最后发现就是忘了这个文件。2.3 推断主类deduceMainApplicationClass()这个方法通过分析当前线程的调用栈找到包含main方法的那个类并将其记录下来。这个主类primarySources通常就是我们传入的YourApplication.class是后续组件扫描的起点。至此SpringApplication实例就准备好了它携带了应用类型、初始化器、监听器等所有“蓝图”信息。接下来真正的启动流程在run方法中展开。3. 启动流程核心SpringApplication.run() 逐层剖析run方法是启动流程的总调度中心。为了清晰我们将其拆解为几个逻辑阶段。public ConfigurableApplicationContext run(String... args) { // 阶段一计时与启动监听 long startTime System.nanoTime(); DefaultBootstrapContext bootstrapContext createBootstrapContext(); ConfigurableApplicationContext context null; configureHeadlessProperty(); SpringApplicationRunListeners listeners getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); // 阶段二准备环境 try { ApplicationArguments applicationArguments new DefaultApplicationArguments(args); ConfigurableEnvironment environment prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); // 阶段三打印Banner Banner printedBanner printBanner(environment); // 阶段四创建应用上下文 context createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 阶段五准备上下文 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 阶段六刷新上下文核心中的核心 refreshContext(context); // 阶段七刷新后置处理空方法留给子类扩展 afterRefresh(context, applicationArguments); // 阶段八计时结束发布启动完成事件 Duration timeTakenToStartup Duration.ofNanos(System.nanoTime() - startTime); listeners.started(context, timeTakenToStartup); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }3.1 阶段一与二环境准备与参数解析在prepareEnvironment方法中SpringBoot根据之前推断的webApplicationType创建对应的环境对象StandardServletEnvironment、StandardReactiveWebEnvironment或StandardEnvironment。然后它会做几件重要的事配置PropertySources将命令行参数CommandLinePropertySource、系统属性System.getProperties()、系统环境变量System.getenv()以及默认的application.properties/application.yml文件加载到环境的PropertySources中并形成一个优先级链。命令行参数优先级最高。处理Profile读取spring.profiles.active配置设置活跃的Profile。这决定了哪些配置文件和Profile注解修饰的Bean会被激活。触发监听器调用SpringApplicationRunListeners.environmentPrepared()通知所有监听器环境已准备就绪。像ConfigFileApplicationListener这样的监听器就是在这里被触发去加载额外的配置文件。ApplicationArguments是对原始命令行参数args的一个封装它提供了更方便的API来访问--开头的参数如--server.port8081和未标记的参数。3.2 阶段四创建应用上下文createApplicationContext()方法根据webApplicationType创建具体的ApplicationContext实例。这是IoC容器的具体实现。SERVLET-AnnotationConfigServletWebServerApplicationContextREACTIVE-AnnotationConfigReactiveWebServerApplicationContextNONE-AnnotationConfigApplicationContext以最常用的AnnotationConfigServletWebServerApplicationContext为例它继承自ServletWebServerApplicationContext内部持有一个WebServer如Tomcat的引用并集成了基于注解的配置能力AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner。3.3 阶段五准备应用上下文prepareContext方法是为容器的刷新做最后的准备工作内容非常丰富设置环境将准备好的Environment设置到Context中。后置处理调用所有ApplicationContextInitializer的initialize方法。这是对上下文进行编程式定制的一个关键扩展点。发布事件触发ApplicationContextInitializedEvent事件。注册单例Bean将SpringApplication自身、ApplicationArguments、Banner等对象以单例Bean的形式注册到容器中。加载源这是关键一步。我们的主类primarySources在这里被加载。具体是通过BeanDefinitionLoader将主类本身注册为一个Bean定义。因为主类通常被SpringBootApplication注解修饰而该注解包含了ComponentScan所以后续在刷新阶段会以这个主类所在的包为起点进行组件扫描。3.4 阶段六刷新应用上下文——IoC容器的灵魂refreshContext最终会调用AbstractApplicationContext.refresh()方法。这是Spring Framework最核心的方法SpringBoot在此基础之上做了增强通过ServletWebServerApplicationContext或ReactiveWebServerApplicationContext重写了onRefresh和finishRefresh方法。我们梳理一下这个经典流程在SpringBoot场景下的关键步骤prepareRefresh()设置启动时间、活跃状态初始化属性源此时环境已就绪。obtainFreshBeanFactory()获取或刷新内部的BeanFactory对于注解配置的上下文这里会创建一个DefaultListableBeanFactory。prepareBeanFactory(beanFactory)对BeanFactory进行标准配置例如设置类加载器、注册几个内置的BeanPostProcessor如ApplicationContextAwareProcessor用于处理Aware接口回调。postProcessBeanFactory(beanFactory)这是一个空方法子类可以重写。在SpringBoot的Web上下文里这里会添加一个WebApplicationContextServletContextAwareProcessor。invokeBeanFactoryPostProcessors(beanFactory)这是自动配置的触发点这一步会调用所有BeanFactoryPostProcessor。其中最关键的是ConfigurationClassPostProcessor它负责处理所有Configuration注解的类。我们的主类上的SpringBootApplication注解会被它解析。SpringBootApplication注解上有一个EnableAutoConfiguration。ConfigurationClassPostProcessor会处理这个注解进而触发AutoConfigurationImportSelector的执行。AutoConfigurationImportSelector会去META-INF/spring.factories文件中查找org.springframework.boot.autoconfigure.EnableAutoConfiguration键对应的所有自动配置类如DataSourceAutoConfiguration,WebMvcAutoConfiguration。这些自动配置类并不会全部生效。每个自动配置类上都有ConditionalOnClass,ConditionalOnProperty等条件注解。SpringBoot会根据当前类路径、环境变量等条件进行筛选最终只加载符合条件的配置类。这就是“按需加载”的自动配置原理。这些自动配置类本身也是Configuration类它们定义的Bean会被解析并注册到BeanFactory中。registerBeanPostProcessors(beanFactory)注册BeanPostProcessor。这些处理器在Bean的初始化前后进行拦截是实现AOP、Autowired、PostConstruct等功能的基石。例如AutowiredAnnotationBeanPostProcessor负责处理Autowired和Value注入。initMessageSource()初始化国际化资源。initApplicationEventMulticaster()初始化事件广播器。onRefresh()SpringBoot扩展的关键方法在ServletWebServerApplicationContext中这个方法被重写用于创建内嵌的Web服务器。它会从BeanFactory中获取一个ServletWebServerFactory类型的Bean默认是TomcatServletWebServerFactory。调用这个工厂的getWebServer()方法创建并初始化一个Tomcat实例配置连接器Connector、上下文路径Context Path等。此时Tomcat已经初始化但尚未启动。registerListeners()注册应用监听器。finishBeanFactoryInitialization(beanFactory)实例化所有非懒加载的单例Bean。这是容器启动过程中最耗时的一步。所有之前注册了Bean定义的类包括我们自己的Service、Controller以及自动配置引入的Bean都会在这里被实例化、属性注入、初始化。依赖注入Autowired在这里发生。初始化方法PostConstruct、InitializingBean在这里被调用。AOP代理也在这里生成如果需要。finishRefresh()SpringBoot扩展的另一个关键方法在ServletWebServerApplicationContext中这个方法被重写用于启动Web服务器。调用之前创建好的WebServer的start()方法。此时Tomcat开始监听端口我们的Web应用正式对外提供服务。发布ServletWebServerInitializedEvent事件。发布ContextRefreshedEvent事件标志着整个容器刷新完成。3.5 阶段七与八收尾工作与Runner执行容器刷新完成后SpringBoot会调用callRunners方法。它会从容器中找出所有ApplicationRunner和CommandLineRunner接口的实现类并按Order注解或Ordered接口定义的顺序执行它们的run方法。重要提示Runner的执行时机是在容器完全刷新、Web服务器已启动之后。这意味着你可以在Runner中安全地调用其他Bean执行一些应用启动后的初始化逻辑比如缓存预热、数据检查等。但请注意此时应用已可对外服务Runner中的逻辑应尽快完成避免阻塞启动过程。最后发布ApplicationReadyEvent事件。监听这个事件与使用Runner的区别在于事件监听是异步的而Runner是同步顺序执行的。4. 自动装配原理的深度拆解自动装配是SpringBoot的招牌特性我们有必要对第3.4节中提到的invokeBeanFactoryPostProcessors步骤里的自动配置加载过程进行更细致的拆解。4.1 SpringBootApplication注解的三位一体查看主类上的SpringBootApplication源码它其实是一个复合注解SpringBootConfiguration EnableAutoConfiguration ComponentScan(excludeFilters { ... }) public interface SpringBootApplication { ... }SpringBootConfiguration本质上就是一个Configuration标明这是一个配置类。ComponentScan指定扫描路径。默认扫描主类所在包及其子包下的Component、Service、Controller等。EnableAutoConfiguration开启自动配置的钥匙。4.2 EnableAutoConfiguration与AutoConfigurationImportSelectorEnableAutoConfiguration注解上有一个Import(AutoConfigurationImportSelector.class)。在Spring处理Import时如果导入的是一个ImportSelector实现类则会调用其selectImports方法来决定需要导入哪些配置类。AutoConfigurationImportSelector.selectImports()方法的核心逻辑是通过SpringFactoriesLoader从META-INF/spring.factories中加载所有EnableAutoConfiguration对应的全限定类名这是一个很长的列表包含了SpringBoot所有官方支持的自动配置。利用AutoConfigurationMetadataLoader加载META-INF/spring-autoconfigure-metadata.properties文件。这个文件里预先定义了每个自动配置类的条件注解信息用于快速过滤提升启动速度。遍历所有自动配置类利用ConditionEvaluator根据类路径、Bean存在情况、环境属性等条件ConditionalOnClass,ConditionalOnBean,ConditionalOnProperty等进行筛选最终得到当前环境下真正需要生效的自动配置类列表。4.3 条件注解自动配置的决策大脑条件注解是自动配置“智能”与否的关键。理解它们有助于我们自定义自动配置或排除某些自动配置。ConditionalOnClass当类路径下存在指定的类时配置才生效。例如DataSourceAutoConfiguration上有ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })只有当你引入了数据库相关的jar包如HikariCP、JDBC驱动这个配置才会被加载。ConditionalOnMissingBean当容器中不存在指定类型/名称的Bean时配置才生效。这是实现“默认配置”和“用户自定义覆盖”的核心。例如SpringBoot默认提供一个DataSourceBean但如果你自己在配置类中定义了一个Bean方法返回DataSource那么SpringBoot的默认配置就会因为此条件不满足而跳过。ConditionalOnProperty当指定的配置属性满足条件时生效。例如很多功能开关都基于此注解。ConditionalOnWebApplication/ConditionalOnNotWebApplication根据应用类型决定。排查技巧当你发现某个自动配置没有按预期生效时可以开启SpringBoot的调试日志debug: true或条件评估报告logging.level.org.springframework.boot.autoconfigureDEBUG。启动日志会打印所有自动配置类的评估结果Positive matches,Negative matches清晰地告诉你哪些配置生效了哪些因为什么条件没生效。这是我排查自动配置问题最常用的手段。5. 内嵌Web服务器的创建与启动流程对于Web应用内嵌服务器的创建与启动是启动流程中极具特色的一环。我们以默认的Tomcat为例深入onRefresh()和finishRefresh()中的细节。5.1 ServletWebServerFactory的获取与定制在ServletWebServerApplicationContext.onRefresh()中会调用createWebServer()方法。private void createWebServer() { WebServer webServer this.webServer; ServletContext servletContext getServletContext(); if (webServer null servletContext null) { // 1. 获取工厂 ServletWebServerFactory factory getWebServerFactory(); // 2. 创建服务器 this.webServer factory.getWebServer(getSelfInitializer()); } else if (servletContext ! null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { // ... } } initPropertySources(); }getWebServerFactory()方法会从BeanFactory中查找ServletWebServerFactory类型的Bean。SpringBoot通过自动配置ServletWebServerFactoryAutoConfiguration根据类路径条件tomcat-embed-core存在向容器注册一个TomcatServletWebServerFactory。定制服务器如果你想修改Tomcat的配置比如线程池参数、连接器属性你有两种主要方式通过配置文件server.tomcat.*下的属性会被自动绑定到TomcatServletWebServerFactory上。通过编程方式声明一个WebServerFactoryCustomizerConfigurableServletWebServerFactory类型的Bean。这是一个定制器接口你可以在其customize方法中对ConfigurableServletWebServerFactory即TomcatServletWebServerFactory进行任意深度定制。Bean public WebServerFactoryCustomizerTomcatServletWebServerFactory tomcatCustomizer() { return factory - { factory.addConnectorCustomizers(connector - { // 定制连接器如设置URI编码 connector.setURIEncoding(UTF-8); }); factory.addContextCustomizers(context - { // 定制上下文 }); }; }5.2 Servlet、Filter、Listener的注册factory.getWebServer(getSelfInitializer())中的getSelfInitializer()返回一个ServletContextInitializer。在SpringBoot中所有Servlet、Filter、Listener的注册最终都通过ServletContextInitializer来完成。SpringBoot通过自动配置DispatcherServletAutoConfiguration向容器注册一个DispatcherServlet的Bean。同时ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean这些包装类它们本身也是ServletContextInitializer。在创建WebServer时SpringBoot会收集容器中所有的ServletContextInitializerBean并在初始化Servlet上下文时调用它们的onStartup方法从而完成所有Servlet组件的动态注册。这也是为什么你只需要用Bean声明一个ServletRegistrationBean就能注册一个自定义Servlet的原因。5.3 端口监听与启动在finishRefresh()中调用startWebServer()最终触发webServer.start()。此时内嵌的Tomcat实例会启动其服务端套接字开始监听server.port配置的端口默认8080。至此你的SpringBoot应用已经成为一个独立的、可执行的Web服务进程。6. 启动过程中的常见问题与调试技巧理解了全流程我们就能系统地分析和解决启动时遇到的问题。6.1 Bean创建失败依赖注入与循环依赖在finishBeanFactoryInitialization阶段如果某个Bean创建失败通常会抛出BeanCreationException。最常见的原因缺少依赖BeanAutowired的Bean不存在。检查该Bean是否被ComponentScan扫描到或者其配置类是否因条件注解未生效。循环依赖Spring默认支持单例Bean的Setter注入和字段注入的循环依赖但构造函数注入的循环依赖会直接报错。错误信息通常很明确“Requested bean is currently in creation: Is there an unresolvable circular reference?”调试方法在IDE中你可以直接在AbstractAutowireCapableBeanFactory.doCreateBean()方法上打断点观察Bean的创建和属性填充过程。查看异常堆栈定位到是你自己代码中的哪个Bean出了问题。6.2 自动配置未生效条件注解不满足如第4.3节所述使用debug: true查看自动配置报告是最直接的方法。报告中的Negative matches部分会详细列出哪些自动配置类因为什么条件被排除了。6.3 端口占用或Web服务器启动失败如果server.port被占用在finishRefresh阶段会抛出WebServerException。你可以通过实现WebServerFactoryCustomizer并添加错误页面或监听器来处理端口绑定失败的情况或者使用server.port0让SpringBoot随机分配一个可用端口。6.4 启动缓慢分析与优化启动慢通常有几个热点组件扫描ComponentScan的包路径过大会扫描很多不必要的类。尽量明确指定扫描范围。类路径扫描SpringFactoriesLoader加载spring.factories以及条件注解评估都需要扫描类路径。减少不必要的依赖jar包可以提升速度。Bean初始化某些Bean的PostConstruct方法或InitializingBean逻辑复杂。考虑使用懒加载Lazy或异步初始化。数据库连接如果DataSource初始化时连接数据库很慢也会拖累启动。检查数据库网络和连接池配置。可以使用Spring Boot Actuator的startup端点需引入spring-boot-actuator依赖并暴露端点来生成一个可视化的启动时间跟踪报告精确找到耗时最长的步骤。启动流程的源码之旅到此告一段落。这个过程看似复杂但每个环节都职责清晰通过事件监听、条件判断、模板方法等设计模式优雅地串联起来。下次当你再执行java -jar命令时脑海中能清晰地浮现出这一幅幅源码执行的画面这才是真正读懂了SpringBoot。