每次看Spring Boot的启动过程,很多人的理解停留在"注解一加、run一发、项目就起来了"这个黑盒层面。真正被面试官问到底层链路,或者线上启动变慢、自动配置不生效的时候,往往说不出个所以然来。我前前后后啃过几轮源码,踩过不少启动相关的坑,这篇文章就把Spring Boot启动原理和相关组件串一遍,从SpringApplication.run()到自动配置、内嵌容器、Starter设计,再到Actuator监控和实战排障,尽量说透,希望能帮你建立一条完整的认知链路。
1. 从SpringApplication.run()说起:启动入口背后的完整链路
1.1 第一步不是new容器,而是感知阶段
很多人以为SpringApplication.run(Application.class, args)直接就是创建容器,其实在创建容器之前有个非常容易被忽略的new SpringApplication(primarySources)阶段。在这个构造函数里,Spring Boot做了三件关键事情。
第一件是推断Web应用类型。代码逻辑很直白:如果javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都在类路径上,就推断为Servlet类型;如果reactor.netty.http.server.HttpServer存在,就是Reactive类型;否则是None。这个推断结果直接决定了后面创建哪种ApplicationContext。
第二件是从spring.factories(以及新版本的AutoConfiguration.imports)读取ApplicationContextInitializer和ApplicationListener的全限定名列表,然后实例化并保存起来。这两类组件在后面的prepareContext阶段会一一发挥作用,很多人启动慢或者监听不到事件,往往就是没有意识到这两类组件在run()方法之外就已经被加载了。
第三件是确定主启动类。这里用的是deduceMainApplicationClass(),它不是随便反射调用栈的,而是遍历当前线程栈帧,找到第一个包含main方法的类。这也是为什么Spring Boot要求主类上必须有@SpringBootApplication,并且通常放在包的最外层——推断出来的主类会配上主类名,后续用于默认扫描包路径的锚点,如果主类位置放错,@ComponentScan默认扫描不到兄弟包里的Bean。
1.2 准备Environment:配置源从哪来
进入run()方法后,首先看到的是StopWatch和SpringApplicationRunListeners。StopWatch是启动计时的关键,它记录了从启动到"假死"的每个阶段耗时,排启动性能问题的时候非常有帮助。
然后是SpringApplicationRunListeners.starting(),这一步触发了最早的监听事件。Spring Boot 2.4以后引入了DefaultBootstrapContext,它先于ApplicationContext存在,给ApplicationContextInitializer提供早期初始化能力,比如配置在bootstrap阶段的上下文加载。这里有个细节:SpringApplicationRunListener是从META-INF/spring.factories里加载的,常见的就是EventPublishingRunListener,它把启动过程中的节点转化成ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationContextInitializedEvent等事件发布出去。
接下来是prepareEnvironment。这一步会把系统属性、系统环境变量、application.properties/yml等的PropertySource按优先级封装成ConfigurableEnvironment。这里有个很多人踩的坑:Spring Boot默认的配置优先级是命令行参数最高,然后是Java系统属性和OS环境变量,最后才是配置文件。而且同一个application.yml里还能用spring.profiles.active激活不同Profile。如果线上环境变量和配置文件里同一个key冲突,很容易出"配置文件改了但没生效"的诡异问题,本质就是没搞懂这一步的PropertySource顺序。
最后在prepareEnvironment末尾,会调用listeners.environmentPrepared(),发布ApplicationEnvironmentPreparedEvent。配置中心客户端、日志系统初始化这些扩展,很多就是监听这个事件来做的。
1.3 创建与刷新容器:启动的真正分水岭
环境准备好了,才轮到createApplicationContext()。如果是Servlet类型,这里创建的是AnnotationConfigServletWebServerApplicationContext,这个类的名字里有ServletWebServer,说明它和后面的内嵌Web容器是绑定的。
prepareContext阶段做三件事:注册BeanDefinition,把主配置类注册进去;执行之前加载好的ApplicationContextInitializer;发布ApplicationPreparedEvent。如果你在启动日志里看不到自定义的ApplicationContextInitializer执行,可能就是这个类的实例化和初始化顺序出了问题。
真正的分水岭是refreshContext(context),它内部调用的是AbstractApplicationContext.refresh(),这一步才是Spring IOC容器完整的加载流程。Spring Boot在onRefresh()阶段会调用createWebServer(),把内嵌Tomcat、Jetty或Undertow创建出来并启动,这也是为什么启动日志里能看到"Tomcat started on port(s): 8080"出现在Bean初始化之后。而SpringApplication里的afterRefresh()在新版本里已经没什么逻辑了,更多是留给子类扩展,大家看源码的时候不需要过度关注。
refreshContext完成后,还会执行callRunners,运行ApplicationRunner和CommandLineRunner,发布ApplicationStartedEvent和ApplicationReadyEvent。到这一步,Spring Boot才真正对外宣布"服务已就绪"。
2. 自动配置的"魔法":@EnableAutoConfiguration与spring.factories如何联动
2.1 @EnableAutoConfiguration的注解层级
@SpringBootApplication是个组合注解,核心是@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个。其中自动配置的关键就是@EnableAutoConfiguration。
@EnableAutoConfiguration本身又用@Import(AutoConfigurationImportSelector.class)导入了一个选择器。这个选择器和我们平时写的@Import导入普通类最大的区别是,它实现了DeferredImportSelector,并且结合了AutoConfigurationGroup对自动配置类做分组、排序和去重。DeferredImportSelector的意义在于:它不会立即import,而是等所有普通配置类都处理完之后再执行,这样用户自定义的配置类有更高的优先级处理顺序,自动配置类的条件判断不会误伤用户Bean。
在老版本里,自动配置类的名单写在META-INF/spring.factories的EnableAutoConfiguration键下面。Spring Boot 2.7开始推荐使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,这个文件就是纯行格式,每行一个自动配置类的全限定名。新版的导入文件好处是:不支持键值对,命名更明确,不会像spring.factories那样被其他框架的key搞混。
2.2 条件注解如何决定配置是否生效
自动配置类加载了不代表全部生效,每个自动配置类上面几乎都打满了条件注解。最常见的组合是@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty。比如DataSourceAutoConfiguration上有@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }),意思是类路径上必须有javax.sql.DataSource,否则整个数据源自动配置直接跳过。
理解条件注解的执行顺序很重要。Spring Boot内部用ConditionEvaluator逐条判断,判断时会用到MetadataReaderFactory读取类的注解元数据,而不是直接加载类,避免了类不存在时ClassNotFound的尴尬。但它也没法做到完全不触发类加载,有些@ConditionalOnClass里写的类如果在编译期就缺失,需要用name属性用字符串指定,否则IDE里编译都会报错。
排除自动配置的方式也有讲究。可以用@SpringBootApplication(exclude = DataSourceAutoConfiguration.class),也可以用spring.autoconfigure.exclude配置项,或者在AutoConfiguration.imports里根本不写。排除了以后,excludeAutoConfiguration会建立一个排除集合,在导入阶段直接过滤掉。实际排查"为什么自动配置没生效"的时候,第一步要看的就是条件是否满足,第二步就是看是否被排除。
2.3 自动配置与自定义Bean的优先级
自动配置设计的一个重要原则是"用户自定义优先"。大量自动配置类上都有@ConditionalOnMissingBean,这个条件的意思是只有容器里没有用户定义的指定类型Bean时,自动配置的Bean才会生效。
举个实际例子:RedisAutoConfiguration里的RedisTemplate用了@ConditionalOnMissingBean(name = "redisTemplate")。如果你自己在配置类里声明一个叫redisTemplate的Bean,自动配置的就会自动退场。但这里有个细节,@ConditionalOnMissingBean对@Configuration类里通过@Bean方法生成的Bean判断比较严格,如果用户Bean是通过@Component扫描进来的,判断时机可能在BeanDefinition注册阶段就已经生效了。如果你在排查"我自己定义的RedisTemplate为什么没生效"时发现好像被覆盖了,可以先看下是不是类路径上额外引入了哪个自动配置类恰好也定义了同类型Bean。
此外,自动配置类之间也有先后顺序,通过@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder来控制。比如RedisAutoConfiguration往往在CacheAutoConfiguration之前,因为缓存自动配置可能依赖Redis的底层连接工厂。顺序不对的话,模块间的依赖Bean就会在启动时出现找不到依赖的问题。
3. 内嵌Web容器与条件装配:为什么Tomcat会自动起来
3.1 ServletWebServerFactoryAutoConfiguration的链式逻辑
Web应用启动起来,最直观的感受是"我什么依赖Tomcat都没配,它怎么就自己跑起来了"。答案藏在内嵌容器自动配置里。
负责这个逻辑的主配置类是ServletWebServerFactoryAutoConfiguration,它上面有@ConditionalOnClass(ServletRequest.class)、@ConditionalOnWebApplication(type = Type.SERVLET),这俩条件保证只在Servlet Web应用里才生效。真正创建具体容器工厂的是通过@Import导入的EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow三个内部配置类,它们是按条件注解二选一/三选一的关系。
EmbeddedTomcat上有@ConditionalOnClass({ Servlet.class, Tomcat.class }),也就是说,只要类路径上有org.apache.catalina.startup.Tomcat,Tomcat就是默认容器。如果同时引入了tomcat-embed-core和jetty-embed-core,则谁先匹配到条件就由类路径里的依赖顺序决定,不过官方强烈不建议同时引入多个内嵌容器依赖。
3.2 类路径检测与条件判断的先后顺序
Spring Boot不是"因为你在pom里加了starter-web所以Tomcat才启动",更准确的表述是:spring-boot-starter-web传递引入了spring-boot-starter-tomcat,进而引入了tomcat-embed-core,于是类路径上就存在Tomcat.class,条件注解判断通过,EmbeddedTomcat配置类生效,返回TomcatServletWebServerFactory。
这个条件装配的顺序发生在容器refresh阶段。当AnnotationConfigServletWebServerApplicationContext.onRefresh()被调用时,会获取ServletWebServerFactory类型的Bean,调用factory.getWebServer(getSelfInitializer())创建Tomcat实例并启动。所以说"容器启动"并不是一个独立于Spring生命周期之外的动作,它就是onRefresh的一部分。
这里要提一个排查Web端口起不来的常见问题:如果看到"Port already in use"或者"Unable to start embedded Tomcat",不一定只是端口被占用。还有可能是你的ServletWebServerFactory被自定义配置覆盖了,比如手动定义了TomcatServletWebServerFactory并设置了自定义TomcatConnectorCustomizer,结果导致默认端口、协议等配置失效。
3.3 手写一个简化版内嵌容器自动配置
为了更好理解,我写过一个简化版的自动配置Demо,思路基本一致。核心就是一个配置类,通过条件注解和导入选择器,把容器工厂选出来:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Tomcat.class }) public class MyEmbeddedTomcatAutoConfiguration { @Bean @ConditionalOnMissingBean public ServletWebServerFactory tomcatFactory() { return new TomcatServletWebServerFactory(); } }然后把这个类写进META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,Spring Boot启动时就会自动装载这个配置类。实际项目里当然比这复杂,比如ServletWebServerFactoryAutoConfiguration还会通过@Autowired把TomcatConnectorCustomizer、TomcatProtocolHandlerCustomizer收集起来用于定制容器。但模式就是这一套:条件判断、工厂模式、缺省Bean。
理解了这层逻辑,面试里遇到"内嵌Tomcat和普通Tomcat有什么区别"这类问题就好答了:普通Tomcat由外部脚本启动,应用通过web.xml或ServletContainerInitializer加载;内嵌Tomcat则是在Spring容器refresh过程中,由工厂创建并启动,生命周期完全由Spring管理。
4. 启动器(Starter)的设计哲学:依赖、命名与传递
4.1 spring-boot-starter的依赖传递机制
很多人把"Starter"和"自动配置"混为一谈,严格说,Starter主要解决的是依赖管理和模块化的问题,自动配置类才是在运行时干活的。一个Starter本质就是一个Maven模块,pom里声明了一批依赖,有的Starter本身没有任何Java代码。
拿spring-boot-starter-web来说,它的pom依赖了spring-boot-starter、spring-boot-starter-tomcat、spring-web、spring-webmvc。因为Maven依赖是传递的,你只要引入一个spring-boot-starter-web,整个Web开发相关的jar包括内嵌Tomcat就全部进入类路径。这也是"自动配置能生效"的原因之一:条件注解需要类存在,而Starter把"类存在"这个前置条件帮你配好了。
依赖管理方面,spring-boot-dependencies作为BOM统一管理版本号。它会指定Spring Framework、Tomcat、Jackson等各组件的版本,用户不用在pom里写版本号,除非有特殊要求需要覆盖。这里要注意的是,依赖传递也会带来"版本冲突"问题,比如同时引入一个老版本的第三方Starter,它内部强依赖某个旧log4j版本,就可能覆盖Spring Boot统一管理的版本,启动时出现NoSuchMethodError。
4.2 自定义一个业务场景Starter
自己写Starter并不神秘,核心就三步:定义自动配置类、写AutoConfiguration.imports、用@ConfigurationProperties配置项绑定。
我之前在团队里给内部消息队列封装过一个mqops-spring-boot-starter,结构是这样的:
目录规范:
mqops-spring-boot-starter (聚合模块) ├── mqops-spring-boot-autoconfigure (自动配置模块) │ ├── src/main/java/com/xx/mqops │ │ ├── MqOpsAutoConfiguration.java │ │ ├── MqOpsProperties.java │ │ └── MqOpsTemplate.java │ └── src/main/resources/META-INF/spring │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports └── mqops-spring-boot-starter (仅pom)自动配置类示例:
@AutoConfiguration @EnableConfigurationProperties(MqOpsProperties.class) @ConditionalOnClass(MqOpsTemplate.class) public class MqOpsAutoConfiguration { @Bean @ConditionalOnMissingBean public MqOpsTemplate mqOpsTemplate(MqOpsProperties properties) { return new MqOpsTemplate(properties); } }配置属性:
@ConfigurationProperties(prefix = "mqops") public class MqOpsProperties { private String brokers; private String topicPrefix; // getter/setter }而mqops-spring-boot-starter模块的pom就一句话:
<dependencies> <dependency> <groupId>com.xx</groupId> <artifactId>mqops-spring-boot-autoconfigure</artifactId> </dependency> </dependencies>使用者只要引入mqops-spring-boot-starter,在yml里配一下mqops.brokers=xxx,Spring Boot启动时就会自动创建MqOpsTemplate。这一套做下来,团队里接入方的配置成本非常低,而且不会暴露内部实现细节。
4.3 启动器命名的坑与约定
Starter的命名有明确官方约定:Spring官方提供的Starter命名是spring-boot-starter-*,第三方自定义Starter推荐使用*-spring-boot-starter。比如mybatis-spring-boot-starter、redisson-spring-boot-starter。如果反着命名成spring-boot-starter-mybatis,会让人误以为这是官方维护的,而且Spring Boot的自动配置扫描也不会因为名字不同就区别对待,只是社区习惯和商标问题。
另外注意自动配置模块里,配置类的类名最好加上AutoConfiguration后缀,放在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里的顺序也影响加载顺序。如果业务自动配置依赖另一个自动配置的Bean,可以用@AutoConfigureAfter指定,否则可能出现Bean找不到的情况。
5. 监控组件Actuator在启动链路中的角色与端口暴露风险
5.1 启动过程中Metrics与HealthIndicator的装配时机
spring-boot-starter-actuator的地位常被低估,它不是一个纯运维组件,它本身也深度参与Spring Boot启动链路。Actuator模块引入后,actuator-autoconfigure里的MetricsAutoConfiguration、HealthContributorAutoConfiguration会在自动配置阶段注册一堆MeterBinder和HealthIndicator。
ConditionEvaluationReportAutoConfiguration会记录所有自动配置类的匹配结果,这也是为什么你能通过访问/actuator/conditions看到哪些自动配置生效、哪些没生效及原因。启动阶段,健康检查本质上是在HealthEndpoint里聚合所有HealthIndicator的检查结果,比如DiskSpaceHealthIndicator、PingHealthIndicator。
一个值得注意的是,HealthIndicator的Bean创建也在IOC刷新过程中完成,但HealthEndpoint通常会等ApplicationReadyEvent发布后才真正对外提供完整的健康状态。这就是为什么线上有时候端口报"UP"但业务还有一个核心线程池没初始化完,因为自定义的初始化逻辑可能放在ApplicationRunner里,而HealthIndicator默认会先于Runner完成。
5.2 Actuator的暴露配置与常见漏洞防御
Actuator最有争议的地方是暴露端点的安全问题。早期版本默认暴露health和info,但很多教程会把management.endpoints.web.exposure.include=*直接抄到生产环境,结果heapdump、env、jolokia这些端点全部开放,轻则泄露数据库密码和密钥,重则被人通过Jolokia和Logback组合直接RCE。历史上相关的漏洞通报不少,网上搜"actuator 漏洞"就能看到一大串。
我的生产配置一般是这样的:
management.endpoints.web.exposure.include=health,info,metrics,prometheus,conditions,startup management.endpoint.health.show-details=always management.endpoint.health.probes.enabled=true management.endpoints.web.base-path=/internal/actuator然后网关或Nginx层面会把这组路径限制在运维网段,并且叠加统一的身份认证。一定要记住:Actuator的shutdown端点即使不在exposure里也不会被访问,但要启用必需management.endpoint.shutdown.enabled=true,生产环境我基本不开启。每个端点都有自己的场景,暴露面越小越好,不是越全越专业。
5.3 启动耗时分析:利用Actuator的startup端点
排查启动慢的问题,除了看日志和--debug,Actuator还有一个低频但实用的端点:/actuator/startup。它依赖SpringApplication.setApplicationStartup()设置的实现,默认是ApplicationStartup的空实现,不会产生任何数据。要在启动类里启用:
public static void main(String[] args) { SpringApplication app = new SpringApplication(DemoApplication.class); app.setApplicationStartup(new BufferingApplicationStartup(2048)); app.run(args); }此时访问/actuator/startup就能看到启动过程中Bean创建、事件发布、自动配置计算的Timeline,能定位是哪个Bean拖慢了启动速度。配合/actuator/conditions看哪些自动配置无效加载,理论上可以把启动耗时优化得肉眼可见。不过BufferingApplicationStartup会占用额外内存,生产上如果长时间开启,注意调整缓冲大小和是否在启动完成后关闭。
6. 启动慢、自动配置不生效、循环依赖——实战踩坑排查记录
6.1 启动慢的常见根因与排查方法
启动慢是我被问得最多的一类问题。Spring Boot启动慢很少是单点原因,我见过的典型案例有这些:
第一个是类路径上存在大量不必要的自动配置。比如一个纯Redis缓存的微服务,因为pom里误加了spring-boot-starter-data-jpa,启动时就会去初始化数据源,解析JPA实体,连DataSourceAutoConfiguration、HibernateJpaAutoConfiguration全跑一遍,耗时可能多出几秒。排查方法是用--debug启动,看启动日志里的Positive matches,哪类自动配置我觉得不该出现,就去pom里查对应的传递依赖。
第二个是懒初始化被全局打开。spring.main.lazy-initialization=true确实能缩短启动时间,但副作用是所有Bean在第一次调用时才创建,启动后第一次请求会非常慢,而且一些依赖Bean之间会出现奇怪的初始化顺序问题。生产环境不建议全局开启,如果真的想优化,可以对特定耗时的@Bean单独设置@Lazy。
第三个是DNS反向解析和网络超时。比如日志Appender里配置了远程Kafka或ES地址,启动时做网络探测,遇到不可达地址会卡很久。排查时可以用jstack抓线程,看启动线程阻塞在哪个InetAddress.getByName调用上。
第四个比较隐蔽,是spring.factories或AutoConfiguration.imports文件里出现了不存在的类会导致启动报错;出现重复自动配置类则可能导致多个同类Bean覆盖,虽然不一定会报错,但会让启动日志显得混乱。
6.2 自动配置不生效的检查清单
遇到"我加了redis starter为什么RedisTemplate是null"这类问题,我建议按这个清单查:
mvn dependency:tree确认依赖真的进来了,检查是否被<exclusions>排除。- 看启动日志和
/actuator/conditions,定位对应的自动配置类是否Positive matches。 - 如果
Negative matches,看条件注解为什么失败,通常是类缺失、属性不匹配、Bean已存在。 - 检查
spring.autoconfigure.exclude,有没有在配置文件里排除过。 - 检查
@SpringBootApplication扫描路径,如果自定义配置类和自动配置类之间有覆盖关系,确认@ConditionalOnMissingBean判断是否被用户Bean抢先满足条件。 - 检查
AutoConfiguration.imports文件路径和格式,新版本要求放在META-INF/spring/下面,否则不生效。
这个清单基本能解决90%的"自动配置不生效"问题。
6.3 循环依赖的触发时机与解决思路
Spring Boot 2.6开始默认禁止循环依赖,这个改动让很多老项目升级时直接启动失败,报错The dependencies of some of the beans in the application context form a cycle。很多人不理解为什么Spring Boot突然"变严格",其实Spring Framework从4.0起就通过三级缓存解决了单例Bean的循环依赖,但它无法解决构造函数注入的循环依赖,而且循环依赖本身是设计问题的信号。
跨模块的A依赖B、B依赖A,这类循环依赖通常不是靠@Lazy塞一下就完事的,它往往意味着职责边界划分有问题。我的建议是:如果是@Async、@Transactional这类代理对象引发的循环依赖,优先考虑拆开,或者用一个中间层门面对象;如果两个Bean只是调用方法,可以通过事件解耦或者把共享逻辑抽到第三个Service里,这样启动链路更干净,也方便测试。
7. 回答面试官:如何讲清楚Spring Boot启动原理
7.1 一条时间线的表述框架
面试被问到Spring Boot启动原理,不需要逐行背源码,但要有时间线意识。我会这样讲:
"Spring Boot启动是建立在Spring Framework容器之上的一个扩展流程。入口是SpringApplication.run(),它先做类型推断和应用类型判断,加载ApplicationContextInitializer和ApplicationListener,然后prepareEnvironment把配置文件、系统属性、环境变量封装成Environment。接着创建对应的ApplicationContext,在prepareContext阶段注册主配置类并调用初始化器。然后调用refresh()进入Spring容器标准生命周期,在onRefresh()时创建内嵌Web容器。refresh完成后再发布ApplicationStartedEvent和ApplicationReadyEvent,整个应用才算启动就绪。"
这个框架能体现你没把Spring Boot当成黑盒,也知道它和Spring Framework的边界。
7.2 面试中的加分细节
能在表述里带出下面几个点的候选人,我通常会多给一些加分:
一是DeferredImportSelector的作用。自动配置类为什么晚于普通配置类加载?因为要等用户配置处理完,避免@ConditionalOnMissingBean判断过早失效。
二是AutoConfiguration.imports和spring.factories的区别。能讲出新版本为什么要换文件格式,说明关注过版本演进。
三是内嵌Web容器的启动并不是"Tomcat独立进程",而是Spring容器onRefresh阶段通过ServletWebServerFactory创建的,生命周期随Spring容器走。
四是SpringApplicationRunListener和SpringApplicationRunListeners的关系,它是Spring Boot自己定义的运行过程监听机制,和Spring的事件监听器是两套东西,但监听器会通过EventPublishingRunListener转发成Spring事件。
五是ApplicationReadyEvent和ApplicationRunner、CommandLineRunner的执行顺序,以及它们在灰度发布、预热缓存场景里的应用。
7.3 常见追问与应答
面试官经常会顺着你的回答往下问这三个方向:
第一个,"自动配置类是如何被加载的?"回答思路是被@Import导入的AutoConfigurationImportSelector读取imports文件,经过过滤、排序、去重后注册为BeanDefinition,再由条件注解逐个决定是否生效。
第二个,"如果自动配置不生效你怎么办?"回答思路是看--debug报告或者/actuator/conditions,判断是条件不满足、被排除、还是用户Bean冲突。
第三个,"Spring Boot启动大概分几个阶段?"回答思路是Environment准备、Context创建、Context刷新、Web容器启动、Runner执行、事件发布这几个阶段,再补充每个阶段的关键组件。
我个人在实际排查和面试中还有一个习惯:尽量自己动手在项目里写一个极简自动配置类,然后靠/actuator/conditions观察它有没有被加载。这样Run点基础再复杂的问题也能落地,因为你对启动的每个环节都有了"亲自动过"的感知。
最后再分享一个小技巧,每次启动都用--debug看一次自动配置报告,别嫌日志长。在我看来,看明白Positive matches和Negative matches的过程,比背十遍源码更能建立对Spring Boot启动原理和组件的真实理解。