
Spring Boot 用了这么多年很多人停留在Spring Boot 真方便的层面真要问一句启动的时候到底发生了什么为什么写一个依赖就能自动装配往往就卡壳了。作为后端开发Spring Boot 的启动过程和自动配置原理是所有高级面试绕不开的硬骨头也是你排查诡异问题的底层能力。这篇文章我不打算给你讲 PPT 式的概念直接带你从源码层面把启动链路和自动配置机制一层层剥开把每一步做了什么、为什么这么做讲清楚。1. 从SpringBootApplication开始启动的入口与开场1.1 三个注解合并背后的设计意图先看最常见的启动写法SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }SpringBootApplication是一个组合注解本质上是把这三个注解打包在一起SpringBootConfiguration继承自Configuration标记当前类是配置类。它和普通Configuration的唯一区别是Spring Boot 内部会用它做自动配置的边界判断防止误扫描到其他配置类。EnableAutoConfiguration自动配置的开关这是重中之重后面单独展开。ComponentScan包扫描默认扫描SpringBootApplication所在包及其子包。你可能会问为什么 Spring Boot 不直接让用户写三个注解合并成一个是简化使用看似只是少写两行但实际意义是将启动约定固化下来。在早期的 Spring 时代配置极其繁琐且容易漏掉某个注解Boot 的哲学就是约定优于配置把标准启动方式定死减少心智负担。这个设计思路贯穿整个启动过程记住它后面很多源码设计都能理解得更顺。1.2 SpringApplication 实例化的隐藏逻辑进入SpringApplication.run()前会先创建一个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(); }重点是deduceFromClasspath()它通过检查 classpath 中是否存在特定类来判断应用类型存在org.springframework.web.reactive.DispatcherHandler且不存在org.springframework.web.servlet.DispatcherServlet和org.glassfish.jersey.servlet.ServletContainer时推断为REACTIVE类型存在javax.servlet.Servlet或org.springframework.web.context.ConfigurableWebApplicationContext时推断为SERVLET类型否则推断为NONE即非 Web 应用。这个推断直接决定了后面创建什么类型的 ApplicationContext。很多人在本地跑单元测试时发现上下文是AnnotationConfigApplicationContext而启动 Web 项目时变成了ServletWebServerApplicationContext原因就在这里。另一个关键点是getSpringFactoriesInstances。它读取的是META-INF/spring.factories文件Spring Boot 内部机制一个大项就是Spring 工厂加载器SpringFactoriesLoader。它做的事情是扫描 classpath 下所有 jar 包里的META-INF/spring.factories提取指定接口对应的实现类名单然后实例化。ApplicationContextInitializer和ApplicationListener就是通过这个机制加载的。这意味着你可以在spring.factories里声明自己的ApplicationListener在 Spring Boot 启动早期就介入上下文初始化。1.3 run() 方法完整链路run()方法通常被称作启动总指挥它的核心执行步骤可以用这段伪代码表示public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch new StopWatch(); stopWatch.start(); // 1. 引导上下文 DefaultBootstrapContext bootstrapContext createBootstrapContext(); // 2. 广播 ApplicationStartingEvent listeners.starting(bootstrapContext, this.mainApplicationClass); // 3. 环境准备 ConfigurableEnvironment environment prepareEnvironment(listeners, bootstrapContext, args); // 4. 打印 Banner printBanner(environment); // 5. 创建 ApplicationContext context createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 6. 准备上下文 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 7. 刷新上下文核心中的核心 refreshContext(context); // 8. 刷新后处理 afterRefresh(context, applicationArguments); stopWatch.stop(); // 9. 广播 ApplicationReadyEvent / ApplicationFailedEvent listeners.started(context); callRunners(context, applicationArguments); listeners.ready(context, applicationArguments); return context; }这里有一个细节值得注意ApplicationStartingEvent是 Spring Boot 生命周期中最先发布的事件因此你的监听器如果想在 Environment 准备前或 BeanFactory 创建前做点什么监听这个事件最合适。而ApplicationReadyEvent是在应用完全启动后发布适合启动后异步加载缓存、预热连接池等操作。我见过不少团队用PostConstruct做数据预加载然后因为执行顺序问题踩坑。如果对时序敏感应该优先依赖生命周期事件而不是无保证的 Bean 初始化顺序。2. 启动过程中的关键节点环境准备、上下文创建与 refresh2.1 prepareEnvironment系统属性、环境变量与配置文件的三层博弈prepareEnvironment做了三件主要的事情获取或创建Environment、配置环境包括PropertySource的加载、listeners.environmentPrepared广播。Spring Boot 的 Environment 体系本身值得展开PropertySource最底层的属性源一对 Key-Value 集合。常见的有MapPropertySource、SystemEnvironmentPropertySource、ServletConfigPropertySource等。MutablePropertySources多个PropertySource的容器Mutable表示可以动态增加、删除。Environment对PropertySources的统一封装对外提供getProperty()和resolvePlaceholders()。配置项的优先级就是MutablePropertySources的顺序决定的。默认情况下优先级从高到低大致是命令行参数SpringApplication 构造时加入SPRING_APPLICATION_JSONSpringApplication 初始化时加入Servlet 参数、ServletConfig 参数Web 容器环境下JNDI 属性Java System 属性System.getProperties()操作系统环境变量application.properties/application.yml根据 profile 分为多份后加载覆盖先加载默认配置这个列表看起来像一堆优先级规则但背后的设计逻辑是越接近使用者的配置越优先。命令行是使用者输入的最优先配置文件是项目内部约定的其次系统环境和 JVM 参数属于运行环境的基础设施最后兜底。这个优先级设计避免了覆盖一处导致全链路失控的问题。2.2 createApplicationContext根据 WebApplicationType 分流之前说过通过 classpath 推断WebApplicationType这里再看具体的创建逻辑protected ConfigurableApplicationContext createApplicationContext() { return this.applicationContextFactory.create(this.webApplicationType); }不同 WebApplicationType 对应不同实现WebApplicationTypeApplicationContext 类型NONEAnnotationConfigApplicationContextSERVLETAnnotationConfigServletWebServerApplicationContextREACTIVEAnnotationConfigReactiveWebServerApplicationContext如果你用的是传统 Spring 的ClassPathXmlApplicationContext你需要在 XML 里配置一堆context:component-scan/、mvc:annotation-driven/而 Spring Boot 这里先创建了一个空的基于注解配置的上下文后续通过代码注册配置类和 Bean 定义来填充。这背后是 Spring Framework 5.x 开始推动的全注解化趋势去掉 XML 的解析开销与配置漂移风险。2.3 prepareContext让空上下文活起来上下文创建好后是空的prepareContext负责往里面放基础物资private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); // 注册 beanNameGenerator、resourceLoader、classLoader 等 if (beanNameGenerator ! null) { context.getBeanFactory().registerSingleton(BeanNameGenerator.class.getName(), beanNameGenerator); } // 获取资源加载器 SetObject sources getAllSources(); // 注册启动类为 BeanDefinition load(context, sources.toArray(new Object[0])); listeners.contextPrepared(context); // 通过 ApplicationContextInitializer 做后置处理 // 打印启动日志、注册 springApplicationArguments、springBootBanner 等 listeners.contextLoaded(context); }关键点在于load()里的AnnotatedBeanDefinitionReader。它会把你传入的Application.class解析成AnnotatedGenericBeanDefinition标注了注解的 Bean 定义注册进 BeanFactory 里。这里有个重要机制启动类里的Configuration注解不只是声明一个配置类它还会触发后续 ConfigurationClassPostProcessor 的解析该处理器会被标注为配置类的处理入口为之后的自动配置铺路。2.4 refreshContext移花接木的经典模板方法refreshContext最终调用的是AbstractApplicationContext.refresh()。这是 Spring 容器初始化的核心方法Spring 框架老将应该很熟悉。Spring Boot 在这里做了一层薄封装主要是把refresh()包裹进synchronized同步块并且通过startupShutdownHook注册关闭钩子。refresh()的 12 个步骤中有几个与启动关联度最高prepareRefresh()初始化属性源、初始化早期事件监听器。obtainFreshBeanFactory()清空并重建 BeanFactory。Spring Boot 中通常直接复用已有的 DefaultListableBeanFactory。prepareBeanFactory()给 BeanFactory 装标准装备包括类加载器、SpEL 表达式解析器、资源编辑器注册表等。postProcessBeanFactory()子类扩展点Servlet 容器环境下会注册 Web 相关的 Scoperequest/session/application。invokeBeanFactoryPostProcessors()此处执行 ConfigurationClassPostProcessor扫描所有配置类解析 Bean、Import、ComponentScan 等注解。自动配置的核心路径就在这一步展开。registerBeanPostProcessors()注册 BeanPostProcessor为后续 Bean 实例化前后的增强做准备。initApplicationEventMulticaster()初始化事件广播器。onRefresh()模板方法ServletWebServerApplicationContext在这里启动内嵌 Tomcat/Jetty/Undertow。finishBeanFactoryInitialization()实例化所有非懒加载的单例 Bean注意是真正的 Bean 实例化不是 BeanDefinition。finishRefresh()完成生命周期处理、发布 ContextRefreshedEvent。如果你之前看过 Spring 源码会发现整条链路就是在invokeBeanFactoryPostProcessors和finishBeanFactoryInitialization两个节点上起作用。Spring Boot 对经典 Spring 最大的贡献不是在 Spring 之外另起炉灶而是把配置类解析和自动配置筛选深度嵌入到 BeanFactoryPostProcessor 的执行环节里。3. 自动配置的秘密AutoConfigurationImportSelector 与条件装配3.1 EnableAutoConfiguration 引入了什么回到组合注解中的EnableAutoConfiguration它的定义如下Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Inherited AutoConfigurationPackage Import(AutoConfigurationImportSelector.class) public interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY spring.boot.enableautoconfiguration; Class?[] exclude() default {}; String[] excludeName() default {}; }Import(AutoConfigurationImportSelector.class)是入口。AutoConfigurationImportSelector实现了DeferredImportSelector这个Deferred延迟非常关键普通的Import类会立即处理而DeferredImportSelector要等其他所有配置类都完成解析后才执行确保自动配置的 Bean 定义不会抢在用户手动定义的 Bean 之前注册从而为用户优先覆盖提供可能性。AutoConfigurationImportSelector的核心方法是selectImports()它返回的是一组自动配置类的全限定名。这些类的来源在不同版本上有巨大差异Spring Boot 2.7 之前读取META-INF/spring.factories中EnableAutoConfiguration键对应的值。Spring Boot 2.7 开始引入了新的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件旧的spring.factories方式仍然兼容但被废弃。Spring Boot 3.x 起完全使用AutoConfiguration.imports方式。为什么 Spring Boot 团队要做这个迁移主要原因是spring.factories文件是大杂烩一个文件里配置了多种工厂类型不管是ApplicationListener、ApplicationContextInitializer还是EnvironmentPostProcessor都堆在一起解析逻辑复杂且容易误加载。拆成独立文件后每个自动配置类只需要单独声明我依赖哪些前置自动配置依赖关系可以更清晰启动时也能通过缓存优化性能。3.2 条件评估自动配置的准入机制AutoConfigurationImportSelector筛选出的自动配置类通常有几十上百个但它们并不会全部生效。每个类上都标注了一组Conditional注解只有全部条件满足时配置才会被加载。常用的条件注解包括ConditionalOnClass/ConditionalOnMissingClassclasspath 中是否存在某个类。ConditionalOnBean/ConditionalOnMissingBean容器中是否已有某个 Bean用于自动配置的让路机制。ConditionalOnProperty配置项是否达到指定的值。ConditionalOnWebApplication/ConditionalOnNotWebApplication当前应用是否为 Web 应用。ConditionalOnExpressionSpEL 表达式条件。执行条件评估的是ConditionEvaluator它在ConfigurationClassParser解析配置类的时候发挥作用。Spring Boot 启动时可以通过debugtrue在日志中输出自动配置报告包括Positive matches生效的自动配置和Negative matches未生效的自动配置及其原因这是排查启动问题的重要工具。3.3 自动配置报告与条件评估顺序如果我们打开自动配置报告看到的 Conditions Evaluation Report 中有两个关键部分Positive matches条件满足自动配置生效。Negative matches条件不满足自动配置未生效并且会明确说明是哪一个条件没通过。这个报告在启动日志中的源码位置是ConditionEvaluationReportAutoConfiguration。它利用BeanFactoryPostProcessor获取容器里所有ConditionEvaluator的评估记录。有了它我们不用猜某个自动配置为何没生效只需看报告即可。条件评估顺序也值得注意ConditionalOnClass优先于ConditionalOnMissingBean被评估。原因是类是否存在判断代价较低而且可以提前淘汰大部分无关配置而ConditionalOnMissingBean必须在容器已经有部分 Bean 注册完成后才能判断代价较高。Spring Boot 通过AutoConfigurationSorter对自动配置类做排序保证这些检查类是否存在的条件先执行节约整体启动时间。3.4 AutoConfigurationPackage 的职责EnableAutoConfiguration上还有一个容易被忽略的元注解AutoConfigurationPackage。它通过Import(AutoConfigurationPackages.Registrar.class)注册一个内部 Bean记录自动配置包的基础包路径。这个路径本质上就是启动类所在的包。Spring Data JPA 的EntityScan和 Spring Boot 对 JPA 实体类的扫描为什么默认以启动类所在包为基准就是因为这个 Registrar 注册了基础包信息后续JpaRepositoriesAutoConfiguration通过AutoConfigurationPackages.get()获取基础包从而确定扫描范围。理解了AutoConfigurationPackage你就能想通另一个问题为什么启动类一定要放在包的根目录如果启动类放在某个子包里ComponentScan和 JPA 扫描都会受限于这个子包很容易出现Bean 注入失败或Repository 找不到的问题。这不是框架 bug而是设计约束。4. 源码实战追踪一个 DataSource 自动配置的完整旅程4.1 我们从 HikariCP 的日志反推执行过程理论部分讲了这么多现在找一个具体例子从头到尾走一遍。以最常见的spring-boot-starter-jdbc为例看看DataSourceAutoConfiguration是怎么被加载和放行的。假设你在pom.xml中只引入了spring-boot-starter-web和spring-boot-starter-jdbc没有自定义DataSourceBean。启动时会打印类似HikariPool-1 - Starting...的日志。这背后发生了什么执行顺序是这样的AutoConfigurationImportSelector.selectImports()读取AutoConfiguration.imports返回全量自动配置类名单其中包含DataSourceAutoConfiguration。ConfigurationClassParser开始按顺序处理这些类。DataSourceAutoConfiguration上标注了ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })这两个类都在 classpath 上来自 HikariCP 和 spring-jdbc条件满足。它的ConditionalOnMissingBean检查容器中是否已有DataSource类型的 Bean此时还没有于是条件满足开始注册内部配置类。DataSourceAutoConfiguration上还标注了EnableConfigurationProperties(DataSourceProperties.class)这一步把spring.datasource.*配置项绑定到DataSourceProperties对象上。PooledDataSourceConfiguration内部定义了一个Bean方法dataSource()使用DataSourceBuilder构建连接池。先检查 classpath 是否有 HikariCP没有则检查 Tomcat JDBC再检查 Commons DBCP2最后检查 Oracle UCP。dataSource()方法返回的 Bean 被注册进 BeanFactory最终被用于 MyBatis、JdbcTemplate 或 JPA 的注入。4.2 DataSourceProperties 配置绑定与属性覆盖的时机这里有一个关键点EnableConfigurationProperties(DataSourceProperties.class)做了什么DataSourceProperties上有大量ConfigurationProperties(prefix spring.datasource)注解标注的字段ConfigurationProperties(prefix spring.datasource) public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean { private ClassLoader classLoader; private boolean generateUniqueName true; private String name; private Class? extends DataSource type; private String driverClassName; private String url; private String username; private String password; // ... }配置绑定的核心是Binder类Spring Boot 2.x 后由ConfigurationPropertiesBindingPostProcessor驱动。它的工作逻辑是遍历所有ConfigurationProperties标注的 Bean将当前 Environment 中的属性值按照Spring Boot 宽松绑定规则填入对应的 JavaBean 字段中。宽松绑定规则意味着spring.datasource.url、spring.datasource.URL、spring.datasource.Url这些写法效果相同在配置文件中使用短横线user-name与驼峰userName可以互相转换。当连接池构建时DataSourceBuilder内部会优先使用DataSourceProperties中显式设置的url、username、password。如果你在application.yml里只写了部分连接参数剩余参数从哪里来它默认从环境变量中解析SPRING_DATASOURCE_URL、SPRING_DATASOURCE_USERNAME等形式这也是为什么部署到 Docker/K8s 时不修改代码就能通过环境变量覆盖配置的原因。4.3 自定义 Bean 是如何挤掉自动配置的反过来看如果用户自己定义了一个DataSourceBeanConfiguration public class MyDataSourceConfig { Bean public DataSource dataSource() { return new HikariDataSource(); } }启动时的过程变为用户自己的配置类在ConfigurationClassPostProcessor里被优先解析因为自动配置类是DeferredImportSelector延迟到最后才处理。容器中此时已经存在一个名为dataSource的 BeanDefinition。DeferredImportSelector处理的DataSourceAutoConfiguration中ConditionalOnMissingBean(DataSource.class)检测到容器已有 DataSource 类型的 Bean条件不满足整个自动配置类及其内部配置类直接跳过。最终只有用户自己定义的dataSource对象被实例化。这个设计解决了谁优先的核心矛盾用户手动配置永远是第一优先级的框架自动配置只是兜底方案。如果你要排除某个自动配置推荐做法是用SpringBootApplication(exclude DataSourceAutoConfiguration.class)或者在配置文件中设置spring.autoconfigure.exclude而不要依赖ConditionalOnMissingBean的隐式行为因为后者会让排查变得隐晦。4.4 自动配置的顺序依赖AutoConfigureBefore / AutoConfigureAfter / AutoConfigureOrder不同的自动配置类之间存在依赖关系。比如MybatisAutoConfiguration依赖DataSourceAutoConfiguration因为 MyBatis 的SqlSessionFactory需要注入DataSource。这就要用到AutoConfigureBefore、AutoConfigureAfter和AutoConfigureOrder来控制顺序。排序发生在AutoConfigurationSorter中它使用图论中的拓扑排序算法结合AutoConfiguration.imports文件里声明的顺序与这些注解得到最终的加载顺序。正因为有拓扑排序系统才能保证DataSourceAutoConfiguration在MybatisAutoConfiguration之前完成配置从而让 SqlSessionFactory 使用到已经构建好的数据源。这里容易出一个问题如果自定义配置类中也需要依赖自动配置产生的 Bean通常你只需要依赖 Spring 的Order和容器刷新顺序即可但如果你用到了ConditionalOnMissingBean又想确保自动配置最后处理务必搞清楚DeferredImportSelector的延迟加载机制不要假设自己的配置一定晚于自动配置执行。5. 常见问题排查与实战技巧5.1 启动慢从哪里下手启动慢通常有几类原因这里按排查优先级列出大量自动配置类参与条件评估但条件判断过重。可以使用-Ddebugtrue启动看 Condition Evaluation Report找到那些Negative matches中数量巨大、且条件评估复杂的类考虑通过spring.autoconfigure.exclude排除无关模块。Bean 实例化慢。主要是连接池初始化、HTTP 客户端预热、加密组件初始化等。监听ApplicationReadyEvent做日志统计或者通过Timed、Micrometer 观测启动耗时。类扫描范围过大。SpringBootApplication默认会扫描启动类所在包的所有子包。如果包结构太宽泛扫描大量无关类会拖慢启动。可以通过ComponentScan的includeFilters、excludeFilters精确定位扫描范围但切记不要随意修改基础包位置。BeanPostProcessor 数量过多。refresh()阶段的registerBeanPostProcessors会实例化所有BeanPostProcessor如果引用了大量中间件产物这部分成本会明显上升。可以使用ApplicationStartup的BufferingApplicationStartup结合 JFR 分析启动步骤耗时。5.2 自动配置莫名不生效排查路线遇到我明明引入了依赖但配置没生效时按以下顺序排查第一步检查启动日志中是否出现Negative matches看清楚被拒绝的具体条件。如果是因为ConditionalOnMissingBean被拒说明容器里已经存在一个同类 Bean可能是你无意识注册的也可能来自其他自动配置。第二步检查依赖是否真的进了最终产物。用mvn dependency:tree查看依赖是否存在冲突或 optional 依赖没有传递。第三步看AutoConfiguration.imports文件是否被正确打包。如果是在自定义 Starter 中写自动配置类检META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports路径是否拼对。第四步确认配置文件所在的包路径是否是启动类的子包。如果是自定义配置文件路径确保spring.config.location生效且没有优先级更高的配置源覆盖。第五步用EnableAutoConfiguration的exclude属性或spring.autoconfigure.exclude属性试排除再启用确认条件评估的时效性。5.3 自定义 Starter 的自动配置类怎么让 Spring Boot 发现自定义 Starter 算是对自动配置机制最直接的实战检验。核心步骤就三步创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件每一行写一个自动配置类的全限定名。com.example.common.config.MyAutoConfiguration在自动配置类上标注Configuration并用条件注解精确控制生效场景。Configuration(proxyBeanMethods false) ConditionalOnClass(SomeService.class) ConditionalOnProperty(prefix example.common, name enabled, havingValue true, matchIfMissing true) public class MyAutoConfiguration { Bean ConditionalOnMissingBean public SomeService someService() { return new SomeService(); } }打成一个普通 jar 包放入项目中后自动被AutoConfigurationImportSelector加载。这里有三个容易踩的坑Configuration(proxyBeanMethods false)自动配置类通常会设置代理关闭从而避免 CGLIB 代理开销同时确保内部Bean方法不会被拦截出问题。自动配置类最好不要让它被普通ComponentScan扫到。否则自动配置类会被当成普通用户配置类提前解析丧失延迟加载优势条件判断也会失真。在自定义自动配置类时建议命名时带上AutoConfiguration后缀并在打包时放在独立的包路径下与业务代码隔离。多个自动配置类之间如果有依赖关系优先使用AutoConfigureAfter/AutoConfigureBefore声明顺序不要依赖AutoConfiguration.imports文件里的书写顺序因为排序算法不保证文件顺序。5.4 从启动日志中学习更多Spring Boot 的启动日志往往能提供非常多的信息。看这段典型日志2025-01-18T10:31:22.12308:00 INFO 12345 --- [main] com.example.Application : Starting Application using Java 21.0.1 on my-machine with PID 12345 2025-01-18T10:31:22.52308:00 INFO 12345 --- [main] com.example.Application : No active profile set, falling back to 1 default profile: default 2025-01-18T10:31:27.22308:00 INFO 12345 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2025-01-18T10:31:27.98608:00 INFO 12345 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-01-18T10:31:28.17608:00 INFO 12345 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path 2025-01-18T10:31:28.35608:00 INFO 12345 --- [main] com.example.Application : Started Application in 6.233 secondsStarted Application in 6.233 seconds后面出现的启动耗时包含 Bean 初始化与 Tomcat 启动但不包含内嵌容器端口绑定。如果你发现这段耗时突然暴增重点看ConditionEvaluationReport和contextLifecycle中的 Bean 初始化耗时分布。值得一提的是Spring Boot 3.x 引入了Observability体系的ApplicationStartup可以借助BufferingApplicationStartup收集启动步骤的详细耗时。在写有性能压测要求的系统时这个指标值得关注。5.5 JVM 层面的启动优化与内嵌容器参数调整启动过程中内嵌 Tomcat 是消耗时间的大头之一。如果项目对启动时间敏感可以考虑在application.yml中配置server.tomcat.threads.max、server.tomcat.accept-count等参数避免默认值过大导致初始线程池开销。如果不需要访问静态资源可以关闭静态资源映射spring.web.resources.add-mappingsfalse。如果不需要 WebSocket在 Web 容器的自动配置类中排除WebSocketServletAutoConfiguration等无关配置。通过-Xverify:none或压缩类指针-XX:UseCompressedClassPointers等方式在特定 JVM 版本中减少类加载开销注意 JDK 版本不同参数含义有差异。这些手段都是优化细节真正影响启动速度的往往是条件评估数量与 Bean 实例化数量。设计的思路是让最终打进 classpath 的依赖尽量精不要追求全家桶式依赖否则再多的启动优化也救不回来。6. 沿着启动链路继续扩展很多人看完自动配置原理后会觉得哦原来如此但一旦要自己动手扩展又会无从下手。这里给一个落地建议在测试项目里自定义一个 Starter从src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports开始往里面加你自己的自动配置类和条件注解再通过mvn install安装到本地仓库用作依赖启动一次。这个过程能把本文讲到的大多数机制实际串一遍比干读源码效果好得多。想再往深处走可以研究这几个方向BeanPostProcessor的执行链Autowired注解的解析、ConfigurationProperties的绑定都是在这条链路上完成的。理解它能解释为什么某些注解不生效。EnvironmentPostProcessor在 Environment 准备阶段干预配置源Spring Cloud 的配置中心就是基于这个接口实现的。Spring Boot 3.x 对 GraalVM Native Image 的支持AutoConfiguration.imports机制在编译期被固定下来为静态分析提供了更清晰的边界。研究这部分能看到 Spring Boot 团队对启动快的未来方向判断。最后分享一个我自己碰过的真实案例。有个项目启动特别慢排查了三天最后发现是SpringBootApplication所在的包被放在了整个代码仓库的根目录ComponentScan直接扫描了整个项目的全部模块里面甚至有大量不是当前服务需要的类。把启动类移动到对应模块的基础包目录后启动时间从 50 秒降到 11 秒。这个案例说明了 Spring Boot 的默认约定虽然好用但你必须真正理解它背后的扫描机制和自动配置边界才能避免在项目发展到一定规模后踩坑。掌握启动过程和自动配置原理不只是为了面试更是为了在面对生产环境疑难杂症时能精准定位问题源头而不是盲目试错。