恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

SpringBoot启动流程全解析:从main方法到容器刷新

  • 首页
  • 资讯中心
  • /
  • SpringBoot启动流程全解析:从main方法到容器刷新

相关资讯

CPython 编译器设计解析:从源码到字节码的完整流水线 2026/9/9 21:04:36
HyperFrames 合成视觉评审指南:以 style-9-prod 样张拆解配色、排版、动效与布局的取舍 2026/9/9 21:04:36
Spec Kit工作流:用需求规格化解决AI编程中的需求漂移问题 2026/9/9 21:04:36

最新资讯

TiDB 基于规则的索引选择优化:Always-Good 启发式与 Skyline Pruning 深度解析
邮件营销未死:精细化运营与自动化实战指南
北京SEO优化效果监控:从排名到转化的全链路指南
2020数学建模C题一等奖经验:中小微企业信贷决策完整复现
管式剖面水分仪与墒情自动采集站:安装调试标定实战经验
MATLAB实现NSGA-II多目标优化算法:非支配排序与拥挤距离详解

今日推荐

基于MongoDB的图书管理系统:数据建模与Spring Boot+Vue实战
Claude Code安装配置全攻略:从零开始用上终端AI编程助手
tmux 会话管理与终端复用:AI 编程工作流的调度中枢实战

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

SpringBoot启动流程全解析:从main方法到容器刷新

发布时间:2026/9/9 21:09:36
SpringBoot启动流程全解析:从main方法到容器刷新 很多Java开发者拿到一个SpringBoot项目最熟悉的操作就是在DemoApplication这个类上点右键Run然后看着控制台一行行日志滚动最后看到Started DemoApplication in 1.5 seconds长舒一口气。但如果你换一个角度把启动日志想象成一场接力赛的每个接力点你会发现SpringBoot的main→run→上下文刷新这条链路本质上就是一条从入口到容器心脏的完整血脉。这篇是SpringBoot源码解析系列的第二篇我会沿着启动流程全链路往下扒从main方法进到SpringApplication再从SpringApplication.run走到AbstractApplicationContext.refresh把启动过程中的每一个关键节点、每一个设计决策、每一个常见坑都讲透。适合两类人一类是已经写过几个SpringBoot项目、想搞清楚启动原理的开发者另一类是准备面试想系统梳理SpringBoot启动流程的人。读完这篇你至少能做到三件事第一启动日志每一行都能对应到源码里的具体位置第二启动失败时能顺着调用链快速定位问题第三面试官再问SpringBoot启动流程是怎样的你能从main方法一路讲到refresh()方法里第几个步骤是干什么的而不是只背几个名词。1. main方法只是起点一个静态方法如何挑起整个SpringBoot1.1 启动类的三合一注解到底代表了什么先看每个SpringBoot项目都会有的启动类代码很简单简单到很多人从来没用正眼打量过它package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }SpringBootApplication是一个组合注解它一次性把三个注解打包了SpringBootConfiguration本质是配置类标记这是一个可被组件扫描识别的配置来源、EnableAutoConfiguration开启自动配置这是SpringBoot最核心的开关、ComponentScan默认扫描启动类所在包及其子包下的所有Component、Service、Controller等Bean。这三个职责会分别在启动流程的不同阶段被消费。自动配置不是main方法阶段处理的而是在refresh()里的invokeBeanFactoryPostProcessors阶段才真正展开。组件扫描也不会在run方法一开始就执行而是等到refresh()阶段由ConfigurationClassPostProcessor统一处理。如果你的启动类不在项目最外层包扫描范围就会出问题很多Bean找不到的诡异问题八成跟这个默认包扫描根搞错有关系。1.2 SpringApplication.run的两次转身SpringApplication.run(DemoApplication.class, args)这一行代码实际上做了两件事先new了一个SpringApplication实例再调用这个实例的run方法。静态run方法内部长这样简化版public static ConfigurableApplicationContext run(Class? primarySource, String... args) { return new SpringApplication(primarySources).run(args); }第一次转身是构造方法它负责完成一系列启动前的探路工作第二次转身是实例方法run它负责真正的启动执行。这两个阶段是分开的意味着你完全可以在项目里手动new SpringApplication(...)然后定制一些东西再调用run这也是SpringBoot留给你做二次扩展的口子。有个冷知识run方法允许传入多个primarySource但在常规项目里通常只会传启动类一个。如果传入多个配置类SpringBoot会按照传入顺序把它们都注册为配置来源。这种写法在写单元测试或者做模块化启动时偶尔会用到我见过有人用这种方式把多个配置类合并在一个进程里启动虽然大部分场景用不到但知道有这个能力总有好处。1.3 deduceMainApplicationClass从调用栈捞出启动类SpringApplication构造方法里有一个非常巧妙的动作——推断主类。源码是这样的private Class? deduceMainApplicationClass() { try { StackTraceElement[] stackTrace new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if (main.equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }这段代码的精妙之处在于不通过任何配置只要在JVM里抛出一个RuntimeException然后遍历当前线程的调用栈找到方法名为main的那一层就能拿到启动类。虽然RuntimeException被new出来又扔掉有点浪费但它的成本极低而且在绝大多数场景下足够可靠。后面run方法里需要确定主配置类、确定组件扫描根包都依赖这个推断结果。这个技巧本身就是可以复用的。比如你想在自己的框架代码里反查到底是谁调用了我的方法不需要显式传Class参数用同样的堆栈遍历思路就能搞定。不过要注意如果在某些特殊类加载器环境下Class.forName可能失败所以源码里异常处理是静默的推断失败也不会导致启动崩溃最多影响后续一些依赖主类的判断逻辑。1.4 main方法的args去了哪里main方法接收的String[] args在启动流程里并没有被丢弃。它会被包装成一个DefaultApplicationArguments对象然后注入到Environment中。也就是说你启动项目时命令行里写的--server.port8081、--spring.profiles.activedev这类参数最终会以属性源的形式进入Spring环境并且在优先级上高于application.yml文件里的配置。这也是为什么SpringBoot官方一直强调命令行参数优先——因为它在属性源排序里就是被放在最前面的。提示如果你在命令行里传了参数但配置没生效先检查参数写法。SpringBoot解析的是--keyvalue格式不是keyvalue漏掉两个连字符是排查配置不生效时最容易翻车的地方。2. SpringApplication实例化构造方法里的三个关键决策2.1 判断应用类型类路径决定你住在哪一栋楼SpringApplication构造方法的第一步是调用WebApplicationType.deduceFromClasspath()判断当前应用是什么类型。这一步决定了后续创建什么样的ApplicationContext。判断逻辑的核心是看类路径上有没有特定类如果类路径上同时存在org.springframework.web.reactive.DispatcherHandler且不存在org.springframework.web.servlet.DispatcherServlet也没有Jersey相关类判定为REACTIVE响应式Web应用。如果类路径上存在javax.servlet.ServletSpringBoot 3.x是jakarta.servlet.Servlet和ConfigurableWebApplicationContext判定为SERVLET传统Servlet Web应用。否则判定为NONE非Web应用比如纯后台任务。你可能会问为什么SpringBoot不直接看pom.xml里引了哪个starter而是要通过类路径猜测原因很简单pom.xml在运行时并不存在类路径才是运行时唯一确定的事实。而且这种类路径感应的机制非常灵活比如你明明引了spring-boot-starter-web但在某个模块里把相关类exclude掉了SpringBoot也能正确降级。这个设计思路在SpringBoot里贯穿始终——不是通过显式配置告诉你我是谁而是通过环境探测自动判断。启动日志里经常出现的那句The following 1 profile is active: dev也是基于环境探测的结果并不是配置文件的强制约定。2.2 从spring.factories批量加载初始化器和监听器SpringApplication构造方法里接下来会做两件事setInitializers和setListeners分别加载ApplicationContextInitializer和ApplicationListener。这两类组件不是手动注册的而是通过SpringFactoriesLoader从classpath下所有META-INF/spring.factoriesSpringBoot 3.x部分移到了新的imports文件中加载。这个机制很好理解你引入的每一个SpringBoot starter jar包里都可能自带一个META-INF/spring.factories文件里面用key-value方式声明了一批如果当前环境满足条件请自动注册的组件。SpringFactoriesLoader负责把这些分散在各个jar包里的配置汇总起来按key分组再实例化。以spring-boot-autoconfigure包为例它的spring.factories里就会声明一堆ApplicationListener比如ClearCachesApplicationListener、ParentContextCloserApplicationListener等。这些监听器在构造方法阶段就被实例化出来但此时它们只是待命状态真正的启动事件要等run方法里一个个发出去。这是理解SpringBoot约定优于配置的关键你不需要在代码里显式注册这些监听器只需要把对应的jar包放在classpath上它就会自动生效。热词里经常出现的springboot自动装配原理本质也是同一套SpringFactoriesLoader机制只是key从ApplicationListener变成了EnableAutoConfiguration。2.3 主类推断完成构造方法收尾构造方法最后调用的就是前面提到的deduceMainApplicationClass()。到这里一个SpringApplication实例就创建完毕了它内部装好了应用类型、初始化器集合、监听器集合、主类信息。这些信息都是为了接下来的run方法做铺垫。需要留意SpringBoot 2.x与3.x在构造方法里的差异SpringBoot 3.x基于Spring Framework 6类路径判断的包名从javax变成了jakarta加载自动配置的方式也从spring.factories逐步迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。但是整体的构造流程骨架几乎没有变所以你如果之前读过2.x的源码切到3.x依然能很快上手。3. run方法执行链路从计时器到上下文创建的九道关卡3.1 启动计时器与SpringApplicationRunListeners实例创建好之后真正的大戏开始了。run方法第一件事就是创建一个StopWatch启动计时器这个计时器记录了整个启动过程的耗时最后那句Started DemoApplication in 1.5 seconds就是它算出来的。然后通过getRunListeners(args)拿到了SpringApplicationRunListeners这是启动事件的总广播器。SpringApplicationRunListener是SpringBoot自己定义的一组启动监听器它的回调方法对应启动流程的关键节点回调方法触发时机通常用来做什么starting刚开始执行run上下文还没创建最早的干预机会记录日志environmentPreparedEnvironment准备完成但上下文还没创建读取配置文件、加入配置数据源contextPreparedApplicationContext创建完成但还没刷新对上下文做最后一次定制contextLoadedBean定义加载完成刷新前检查bean定义是否完整started上下文刷新完成runner调用前启动后的清理工作ready所有runner执行完对外宣告应用已就绪failed启动过程中任意异常输出错误诊断信息这些事件本身就是SpringBoot扩展体系的一部分。如果你在一个starter里实现了SpringApplicationRunListener并在spring.factories里注册就能在最早期拿到启动事件做一些业务无关的横切逻辑比如链路追踪的启动埋点。3.2 prepareEnvironment环境准备与配置文件的读取run方法里第二个大动作是prepareEnvironment。这一步会创建一个StandardServletEnvironmentWeb环境下然后把命令行参数、系统属性、环境变量等属性源都绑定进去最后发布environmentPrepared事件。关键点在于application.yml文件的加载并不是在prepareEnvironment一开始就发生的而是在environmentPrepared事件里由ConfigDataEnvironmentPostProcessor完成的。也就是说当你看到启动日志中出现No active profile set, falling back to 1 default profile这类信息时配置数据已经被处理完了。配置优先级问题与这里密切相关。SpringBoot的配置优先级大致是命令行参数 Java系统属性 操作系统环境变量 application-{profile}.ymlapplication.yml 默认属性。很多人排查为什么我改了application.yml不生效时常常忽略命令行参数里残留的--server.port这就是因为在属性源排序里命令行的位置太靠前了。3.3 Banner打印一种容易被忽略的启动信号run方法接下来会打印Banner。SpringApplicationBannerPrinter会先找classpath下的banner.txt如果找到就打印自定义内容找不到就打印默认的SpringBoot字符画。Banner支持三种模式console、log、off通过spring.main.banner-mode配置控制。这个环节看似无关紧要但在生产环境排查问题时其实有点价值如果你配置了自定义Banner每次部署上线后看一眼日志开头的Banner能不能正常打印就能快速判断新版本是否真的启动了。毕竟有些故障是旧进程还没退出新进程没绑上端口Banner可以帮你在第一时间确认当前日志属于哪个进程。网上还有不少Banner在线生成器把ASCII字符画塞到banner.txt里团队内部做项目标识还挺有意思的。Banner还有一个隐藏细节如果你在banner.txt里写了${application.title}、${application.version}这类占位符它是支持从Manifest文件里解析应用名和版本号的。这个功能在自动化发布场景里很有用但前提是你打包插件正确生成了Manifest信息否则占位符不会被替换。3.4 createApplicationContext根据应用类型选择容器Banner打印完run方法调用createApplicationContext创建容器。这里会根据之前推断出的WebApplicationType选择不同的实现类应用类型ApplicationContext实现SERVLETAnnotationConfigServletWebServerApplicationContextREACTIVEAnnotationConfigReactiveWebServerApplicationContextNONEAnnotationConfigApplicationContextAnnotationConfigServletWebServerApplicationContext本身是ServletWebServerApplicationContext的子类后面会讲到它重写了onRefresh方法专门用来内嵌Web服务器。这也是SpringBoot为什么能内嵌Tomcat的容器层答案不是Tomcat主动跑起来而是Spring容器在刷新到某一个阶段时主动去创建一个TomcatServletWebServerFactory再由这个工厂启动Tomcat。创建完上下文之后run方法还会给上下文设置环境、绑定资源加载器和类加载器。这步看起来琐碎但少了任何一环后面Bean定义扫描时的类加载都会出问题。3.5 prepareContext刷新前的最后一个大动作在真正执行refresh()之前run方法还会调用prepareContext这是启动流程里信息量最大的一个阶段。它的职责主要有四块第一注册beanNameGenerator。默认情况下SpringBoot使用AnnotationBeanNameGenerator也就是把类名首字母小写作为bean名。如果你的业务Bean有重名情况可以通过ComponentScan(nameGenerator ...)覆盖大部分项目用不到但要知道这个扩展点存在。第二执行所有ApplicationContextInitializer的initialize方法。前面说构造阶段只是加载了这些初始化器它们真正发挥作用是在这里。比如Spring Cloud的BootstrapApplicationListener就是在prepareContext阶段识别spring.cloud.bootstrap.enabled配置把父上下文和引导上下文挂载进去的。第三把我们在main方法传入的启动类primarySources注册为Bean定义。这一步保证了SpringBootApplication注解所在的类能作为配置类进入后续的刷新流程。第四依次发布contextPrepared和contextLoaded事件。contextPrepared表示上下文已经创建、环境已经就绪但还没有刷新contextLoaded表示Bean定义已经加载完毕此时还没有实例化任何单例Bean这两个事件是SpringBoot提供给你的最后干预点。3.6 refreshContext把接力棒交给Spring核心容器prepareContext完成之后run方法调用refreshContext(context)。这一步内部很简短先注册JVM关闭钩子如果你没有显式关闭然后调用refresh(context)。关键就在这个refresh里它是Spring Framework容器生命周期里最核心的模板方法。从这一步开始控制权从SpringBoot的SpringApplication移交给了Spring Framework的AbstractApplicationContext。也就是说SpringBoot启动流程的前半部分是SpringBoot自己的事后半部分则完全是Spring原生的容器刷新逻辑只不过SpringBoot在容器刷新过程中悄悄塞入了自动配置解析、内嵌Web服务器创建这些扩展点。4. refresh()才是真正的主题AbstractApplicationContext的十二步刷新法4.1 刷新前的准备与前两步prepareRefresh和obtainFreshBeanFactoryAbstractApplicationContext.refresh()是Spring Framework中一个经典的模板方法整个方法按照固定顺序执行十二个步骤。SpringBoot启动的成败基本都取决于这十二步执行过程中有没有抛出异常。第1步prepareRefresh把上下文标记为活跃初始化属性源占位符检查必填属性是否缺失。这一步还会初始化earlyApplicationEvents用来缓存那些在监听器还没注册完成前就要发布的事件。SpringBoot里很多启动时事件丢失的问题都是因为事件发布早于监听器注册earlyApplicationEvents就是用来兜底的。第2步obtainFreshBeanFactory对内部的BeanFactory做一次重置——销毁已有Bean关闭旧的BeanFactory然后创建新的DefaultListableBeanFactory并设置是否允许Bean覆盖、是否允许循环引用等开关。这一步之所以叫刷新是因为每次refresh()都会先重建一次BeanFactory保证容器状态干净。4.2 prepareBeanFactory和postProcessBeanFactory给BeanFactory装上默认装备第3步prepareBeanFactory是给刚创建的BeanFactory装上默认装备。这一步会为BeanFactory配置类加载器、表达式解析器StandardBeanExpressionResolver、资源编辑器注册表并注册一些内建的Bean后置处理器比如ApplicationContextAwareProcessor、ApplicationListenerDetector。还会注册几个特殊的单例Beanenvironment、systemProperties、systemEnvironment。这步做完BeanFactory才具备最基本的Spring能力。第4步postProcessBeanFactory是模板方法里留给子类扩展的点。AnnotationConfigServletWebServerApplicationContext会在这里注册一堆注解相关的后置处理器还会扫描classpath下残留的注解配置类。这一步很隐蔽但如果你的项目里既有传统的web.xml配置又有注解配置很可能就是在这个阶段被合并处理的。4.3 invokeBeanFactoryPostProcessors自动装配的真正触发点第5步invokeBeanFactoryPostProcessors是启动流程里最核心的一步没有之一。SpringBoot的自动装配原理、配置类解析、组件扫描全都在这一步完成。执行逻辑是先从BeanFactory里找出所有BeanDefinitionRegistryPostProcessor类型的Bean按优先级排序后依次执行。这中间排在最前面的就是ConfigurationClassPostProcessor——它是Spring处理Configuration注解的后处理器优先级最高。它拿到主启动类后会解析SpringBootApplication里的三个注解逻辑其中EnableAutoConfiguration通过Import(AutoConfigurationImportSelector.class)把SpringBoot的自动配置类拉进来。AutoConfigurationImportSelector的selectImports方法会去classpath下加载所有META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件新版或spring.factories中的EnableAutoConfiguration配置旧版拿到候选自动配置类名单然后经过ConditionalOnClass、ConditionalOnMissingBean等条件注解逐一过滤最终把符合条件的自动配置类注册为Bean定义再解析这些自动配置类内部的Bean方法。这也是为什么自动配置类里大量使用ConditionalOnMissingBean的原因——它允许你自定义Bean只要你在应用代码里定义了同类型Bean自动配置就会主动退让。理解了这一步你就能明白自动配置不生效和你的Bean没被扫描到其实是两回事前者是自动配置类的条件不满足后者是组件扫描路径出了问题。4.4 registerBeanPostProcessorsBean实例化前的最后部署第6步registerBeanPostProcessors会把所有BeanPostProcessor类型的Bean注册到BeanFactory里。这一步不执行后置逻辑只是注册真正调用是在后续Bean实例化过程中。BeanPostProcessor是Spring里最强大的扩展点之一Autowired的依赖注入、Value的占位符解析、AOP代理的创建都是通过不同的BeanPostProcessor在Bean实例化前后完成的。如果你在项目里写了一个实现BeanPostProcessor的类它的注册时机就在这一步所以它的执行顺序天然早于业务Bean的初始化。4.5 initMessageSource和initApplicationEventMulticaster容器内建基础设施第7步initMessageSource初始化国际化资源。如果容器里没有MessageSource类型的BeanSpring会创建一个默认的空实现如果项目里定义了MessageSource这里会直接使用。第8步initApplicationEventMulticaster初始化事件多播器。Spring的事件发布机制依赖一个ApplicationEventMulticasterBean默认是SimpleApplicationEventMulticaster。如果你需要在事件监听器里使用异步处理可以从这一步入手自定义一个TaskExecutor并注入到SimpleApplicationEventMulticaster中。我之前在一个物联网项目里就是这么做的把设备状态变更事件改成异步广播后接口响应时间下降非常明显。4.6 onRefresh内嵌Web服务器在这里真正启动第9步onRefresh是模板方法留给子类扩展的最后一个大钩子。在ServletWebServerApplicationContext里这一步会调用createWebServer()也就是在这里SpringBoot开始创建内嵌Tomcat/Jetty/Undertow。创建流程大致是先从容器里找ServletWebServerFactory类型的Bean找到TomcatServletWebServerFactory后调用它的getWebServer方法创建Tomcat实例、设置端口、初始化Context最终调用tomcat.start()把HTTP服务跑起来。所以启动日志里看到Tomcat started on port(s): 8080 (http)时Spring的BeanFactory已经准备好了一大半但业务Bean还没正式创建。理解这一步的意义在于当你遇到端口被占用的报错时不要只盯着端口配置要意识到onRefresh阶段是在Spring容器刷新的中途说明你的Bean定义解析已经没问题只是Web服务器启动失败了。端口问题的排查顺序应该是先看server.port配置是否生效再看是否有多个实例抢同一个端口最后检查防火墙和Docker端口映射。4.7 finishBeanFactoryInitialization和finishRefresh最后的临门一脚第10步registerListeners把之前收集到的所有静态监听器注册到多播器里并处理那些缓存的早起事件。第11步finishBeanFactoryInitialization把BeanFactory设置为已配置完成然后开始实例化所有非懒加载的单例Bean。这一步是用户代码里Service、Repository、Component这些Bean真正创建的地方。如果某个Bean的构造函数或PostConstruct方法抛异常启动就会在这里失败。这也是为什么启动时报错但找不到原因时应该重点看这个阶段抛出的异常堆栈。第12步finishRefresh执行生命周期处理调用lifecycleProcessor.onRefresh()让实现了SmartLifecycle接口的Bean收到启动回调清理资源缓存发布ContextRefreshedEvent事件。如果你在项目里写过ApplicationListenerContextRefreshedEvent它就是在这一步开始被调用的。refresh()执行完毕后run方法还会做几件收尾工作调用afterRefreshSpringBoot里默认空实现然后发布started事件依次调用容器里所有的ApplicationRunner和CommandLineRunner最后发布ready事件返回ConfigurableApplicationContext。所以SpringBoot官方定义里ApplicationRunner的执行时机是上下文刷新完成之后、对外宣告就绪之前非常适合做数据初始化、缓存预热等收尾任务。5. 版本差异与启动异常排查这些坑我踩过你也可以绕开5.1 SpringBoot 2.x与3.x的启动链差异我把近两年实际项目里踩过的版本坑整理一下。SpringBoot 3.x相比2.x在启动链路层面的差异主要集中在这三个地方对比项SpringBoot 2.xSpringBoot 3.x基础框架Spring Framework 5.xSpring Framework 6.xJava最低版本Java 8Java 17Servlet命名空间javax.servletjakarta.servlet自动配置声明路径META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports循环引用默认策略默认允许默认禁止需要显式开启第三个差异是真的容易踩坑。SpringBoot 3.x里自动配置类不再从spring.factories读取而是从AutoConfiguration.imports里读取。如果某个老版本第三方starter还在用spring.factories方式声明自动配置在SpringBoot 3.x里就会静默失效。网上那些升级3.x后某些自动配置不生效的问题大部分是第三方库兼容性导致的。第二个差异更隐蔽。SpringBoot 3.x对循环依赖默认报错如果你之前在2.x里靠循环依赖侥幸运行的项目直接升级大概率会在finishBeanFactoryInitialization阶段遇到Currently in creation chain的异常。解决办法不是上来就改设计而是先通过启动日志确认循环引用的Bean是哪几个再决定是调整依赖关系还是临时开启spring.main.allow-circular-referencestrue。5.2 版本太高、NoSuchMethodError与依赖冲突的定位链路热词里出现springboot版本太高实际编码中更常见的症状是NoSuchMethodError。我见过一个真实案例项目里手动引入了一个高版本的spring-web同时SpringBoot父pom引入的是另一个版本启动时在DispatcherServlet初始化阶段直接抛NoSuchMethodError。这类问题的排查链路一定要从启动流程的依赖加载入口看起。SpringBoot启动后classpath下如果存在多份不同版本的Spring jar包类加载器只会加载其中一个而加载到的是哪个取决于classpath的jar包顺序这几乎是不可控的。所以最稳妥的方案是所有Spring相关依赖都交给spring-boot-starter-parent或spring-boot-dependencies的BOM管理业务模块里只有极少数情况才需要显式指定版本。如果你已经遇到NoSuchMethodError我建议的排查步骤是在IDE里打开报错的那个类确认它编译时依赖的jar包是哪个。在Terminal执行mvn dependency:tree -Dincludesorg.springframework:spring-web查看spring-web的实际版本树。确认有没有被其他starter的传递依赖带上来的旧版本。在pom中用dependencyManagement统一锁定版本。5.3 启动流程里编译器未包含main类型这类问题出在哪热词里还有一条编译器未包含main类型这个报错一般不在SpringBoot源码层面而是环境或构建层面的问题。它的触发链路是JVM从启动类里找public static void main(String[] args)方法但class文件里没有这个方法就会抛Main method not found。排查时先别急着怀疑源码按这四步走确认启动类本身的main方法签名是不是写错了比如多写了一个throws Exception没关系但static丢了一定不行。确认mvn clean compile后target/classes里有没有DemoApplication.class文件。确认IDE的Project Structure里src/main/java是否被标记为Sources Root。确认编译器输出目录是不是被IDE改到了某个奇怪的路径导致运行时加载的不是当前编译产物。这类问题跟SpringBoot启动流程关系不大但因为它拦截在main方法之前很多人一开始就误入歧途去读SpringApplication源码反而找不到原因。5.4 一套自动配置是否生效的实测排查法最后分享一个排查自动配置失效的三步法这是我从几次线上问题里总结出来的。第一步看启动日志。把application.yml里的日志级别改为debug: true重启后在日志里搜索Positive matches和Negative matches。Positive matches列出了所有生效的自动配置类及匹配原因Negative matches列出所有不生效的配置类及不匹配条件。这一步基本能定位90%的问题。第二步如果日志级别不能开可以在启动类里临时加一个ApplicationRunner通过AutowireCapableBeanFactory或ContextRegistry打印容器里实际注册了哪些关键Bean比如ServletWebServerFactory是否存在。这种方式比看自动配置报告更直接。第三步确认排除路径。在SpringBootApplication(exclude ...)里显式排除的自动配置类以及spring.autoconfigure.exclude配置指定的类都会导致自动配置不生效。排查时先搜一下这几处有没有写错类名。提示自动配置不生效不等于错误。SpringBoot的所有自动配置都受ConditionalOnXxx约束条件不满足时选择不装配本来就是设计行为。你先确认你以为该生效的配置是否真的满足条件再动手改能省下很多时间。写在最后用打断点的方式重新读一遍源码我个人在实际操作中的一个体会是读启动流程源码不要从第1行读到第1000行那只会越读越晕。真正有效的方式是拿一个最简单的DemoApplication在SpringApplication.run方法第一行打一个断点然后一步一步往下走每到一个关键节点做一次总结。第一次走完SpringApplication的run再往下走到refresh()第二次走完整个AbstractApplicationContext.refresh。这个过程中你会发现启动日志的每一行输出都开始有了具体的代码位置再去看任何启动层面的报错都会觉得心里有底。还有一个值得扩展的方向在你理解了启动全链路之后再回头去看那些所谓的SpringBoot面试题比如SpringBoot自动装配原理SpringBoot启动过程SpringBoot如何内嵌Tomcat你会发现它们其实都在讲同一件事——SpringApplication如何把Spring Framework的refresh流程与SpringBoot的生态能力绑定在一起。从这个角度切入面试时你会比别人多一层底层视角的表达优势。如果你准备动手跟读一遍源码建议先从SpringBoot 2.7这个版本入手它兼容性好、文档多、社区踩坑记录全等链路熟悉了再切3.x看差异会顺利很多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号