Spring Boot启动失败排查:Error starting ApplicationContext
这两天在帮一个同事排查项目启动问题他刚把工程从网上下下来一跑就给我看了一段日志开头第一句是“Error starting ApplicationContext”后面紧跟着一句“To display the conditions report re-run your application with debug enabled”。他挺懵的说网上搜了半天有人让改端口有人让换数据库配置还有人让删掉某个自动配置类但试了一圈都没用。这个报错我太熟了。它不是某个具体异常的名字而是Spring Boot启动失败的“总提示”后面跟着的“conditions report”才是它给你的破案线索。版本升级、依赖冲突、配置缺失、Bean创建失败只要ApplicationContext在初始化过程中出了岔子都会被这句话盖在头上。如果你也被这句话卡住过或者想搞懂它到底在让你查什么这篇文章应该能帮你省不少时间。1. 报错到底在提示你什么先别急着瞎改配置很多新手看到“Error starting ApplicationContext”第一反应是去搜这句话搜出来一堆跟自己场景完全不搭的解决方案越弄越乱。其实这句话只是一个“帽子”真正的错误原因在它下面甚至在更早的日志里。1.1 这段英文提示的真实含义Spring Boot启动的时候会创建一个ApplicationContext整个容器初始化包括配置加载、Bean定义扫描、自动配置生效、Bean创建、内嵌Web服务器启动等一堆动作。这个过程中任何一个环节抛了异常Spring Boot都会把异常信息汇总在最顶部打出一行类似这样的日志Error starting ApplicationContext. To display the conditions report re-run your application with debug enabled.后面一般还会跟着org.springframework.context.ApplicationContextException: Failed to start bean webServerStartStop; ... org.springframework.beans.factory.BeanCreationException: Error creating bean with name xxx defined in class path resource ...如果你用的是较新的Spring Boot版本报错后面可能还会有一个专门的“APPLICATION FAILED TO START”段落里面带着Description和Action两部分告诉你大致发生了什么以及应该怎么处理。所以你要明白这一句“Error starting ApplicationContext”本身不携带根因它是在告诉你“容器初始化中止了”。真正的原因在后面的异常堆栈里或者在那句APPLICATION FAILED TO START的说明里。不要因为它出现在日志最前面就把注意力全放在这句话本身。1.2 为什么Spring Boot不直接告诉你“哪一行代码错了”有人会问为什么不在这一句旁边直接把异常原因写出来其实写了只是被这行提示挡住了。Spring Boot的异常包装层级比较深一个最底层的数据库连接失败可能会被包装成“创建名为dataSource的Bean失败”再被包装成“Bean初始化失败”最后才变成“ApplicationContext启动失败”。给你举个例子。你配置的数据库密码错了启动时抛出的原始异常是Access denied for user rootlocalhost (using password: YES)但它会层层包裹最底层java.sql.SQLException: Access denied...中间层org.springframework.beans.factory.BeanCreationException: Error creating bean with name dataSource...顶层Error starting ApplicationContext这时候如果你只盯着顶层的Error starting ApplicationContext当然找不到真正原因。正确姿势一定是往下翻找第一处Caused by那才是问题源头。StackOverflow上老鸟让你贴完整日志就是在帮你找这个Caused by。所以遇到这个报错别慌别急着改配置先把完整日志从头到尾翻一遍。很多时候原因比你想象中简单得多只是被日志的顺序和包装绕晕了。2. 先按提示打开debug模式拿全完整诊断报告那句“re-run your application with debug enabled”并不是让你去查一个叫conditions report的文档而是让你把Spring Boot的debug开关打开重新跑一次让它在启动的时候把一份叫做“自动配置评估报告”的东西打印出来。2.1 开启debug的两种简单方式第一种方式在application.properties里加一行debugtrue如果你用的是application.yml写法是debug: true第二种方式启动时追加命令行参数java -jar your-app.jar --debug如果你用IDEA跑在“Program arguments”里填上--debug就行。注意这里的debug不是Java远程调试开关也不是把日志级别调到TRACE它是Spring Boot专门用来控制“自动配置诊断报告”开关的属性。开启之后控制台会打出一份很长的CONDITIONS EVALUATION REPORT这个报告会告诉你哪些自动配置类匹配上了哪些没匹配上为什么匹配或没匹配。提醒一句这个开关会输出大量日志正式环境千万别开着跑排完问题马上关掉。2.2 怎么快速读懂CONDITIONS EVALUATION REPORT打开之后控制台里会出现类似下面这样的内容 CONDITIONS EVALUATION REPORT Positive matches: ----------------- AopAutoConfiguration matched: - ConditionalOnClass found required classes org.springframework.context.annotation.EnableAspectJAutoProxy, org.aspectj.lang.annotation.Aspect (ConditionalOnClass) Negative matches: ----------------- ActiveMQAutoConfiguration: Did not match: - ConditionalOnClass did not find required class javax.jms.ConnectionFactory (ConditionalOnClass)Positive matches表示哪些自动配置类生效了比如AOP、MVC、Jackson等。Negative matches表示哪些自动配置类没生效并且会列出具体没匹配上的条件。这里有个很常见的误区看到Negative matches里面一堆东西以为它们都是错误。其实大部分没匹配是正常的比如你项目里没有引入ActiveMQ相关依赖ActiveMQAutoConfiguration当然不会生效。只有当你怀疑某个功能应该生效却没生效时才需要去Negative matches里找线索看它是因为缺哪个类或哪个配置才没匹配上。2.3 Negative matches和Positive matches里藏着哪些线索举一个我实际遇到过的情况。项目里引入了一个第三方jar包它的某个配置类用ConditionalOnClass判断当前classpath里有没有某个类结果那个类因为maven依赖冲突被排掉了导致整个配置类不生效启动时业务代码引不到Bean直接报BeanCreationException。这种情况下你光看异常堆栈只能看到“某个Bean创建失败”到底为什么失败需要翻报告。在Negative matches里搜那个配置类就能看到一条类似SomeAutoConfiguration: Did not match: - ConditionalOnClass did not find required class com.xxx.SomeNeededClass (ConditionalOnClass)看到这里你就明白了不是代码写错是类根本没在classpath里。下一步去查依赖树看那个jar被谁排掉了。所以这份报告的作用就像黑盒诊断仪它不会直接告诉你“哪个配置写错了”但会告诉你“哪个自动配置类加载了、哪个没加载、判断依据是什么”。拿它和异常堆栈对着看大部分启动问题都能定位。3. “APPLICATION FAILED TO START”后面那几行是定位根因的主战场如果你打开日志往下翻会发现Spring Boot接着打了一个框架感很强的段落通常长这样*************************** APPLICATION FAILED TO START *************************** Description: The Tomcat connector configured to listen on port 8080 failed to start. The port may already be in use. Action: Stop the process thats listening on port 8080 or configure this application to listen on another port.有些版本没有这个段落只有一个长的异常堆栈。但不管你用的是2.x还是3.x最关键的信息一定出现在“Description”和“Action”这两块或者在深层的Caused by里。3.1 从Description、Action到Cause by的阅读顺序我每次排查这类报错的顺序是固定的先搜APPLICATION FAILED TO START看有没有Description和Action。如果没有就搜Caused by一层一层往下找定位到最底层那个异常。如果最底层异常跟某个特定配置相关再去翻配置文件或依赖。如果还是找不到方向再结合conditions report去判断自动配置哪些没生效。为什么先看Description因为Spring Boot在识别到一些常见问题时会给你非常直白的建议。比如数据库连接没配置成功它会直接说Description: Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured. Action: Consider the following: If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. If you have database settings to be loaded from a particular profile you may need to activate it.看到这种话基本不用动脑照着“Action”做就行。但更多情况下Description只会告诉你一个Bean名字真正原因得继续往下挖。3.2 一个经典案例数据源配置缺失导致的启动失败说个很典型的例子。有个同事自己建了一个Spring Boot工程引入了spring-boot-starter-data-jpa又引入了MySQL驱动但application.yml里面根本没有配置数据源信息。启动时日志第一行就是Error starting ApplicationContext他当时完全不知道发生了什么把日志截图发到群里问。我让他把完整日志发过来往下翻看到*************************** APPLICATION FAILED TO START *************************** Description: Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured. Action: Consider the following: If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. If you have database settings to be loaded from a particular profile you may need to activate it.原因一下就清楚了项目里带了数据源相关自动配置但没有提供spring.datasource.url。Spring Boot不知道连哪个数据库干脆把启动过程停掉避免后续操作里出现更诡异的问题。解决办法也很直接在application.yml里补上数据库连接信息spring: datasource: url: jdbc:mysql://localhost:3306/test?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver这里有个细节MySQL的高版本驱动类名是com.mysql.cj.jdbc.Driver老版本是com.mysql.jdbc.Driver。如果驱动包版本和驱动类名不匹配同样会启动失败。所以遇到数据源类报错优先检查三点依赖有没有引入、url和账号密码有没有配对、驱动类名跟驱动版本是不是匹配。3.3 debug报告只是辅助显性异常才是判案核心有些人一看到这个报错就急着开debug结果打开后看到满屏的“Positive matches”和“Negative matches”反而更晕。我的看法是debug报告确实有用但它通常只是辅助真正的判案核心还是那几行显性异常日志。举个例子Bean创建失败导致启动中止时异常堆栈里通常会有类似这样的描述Description: Field orderService in com.example.controller.OrderController required a bean of type com.example.service.OrderService that could not be found. Action: Consider defining a bean of type com.example.service.OrderService in your configuration.这种提示非常友好它直接告诉你OrderController里有个字段想注入OrderService但是容器里没有这个Bean。那你该做的事就非常明确看看OrderService有没有加Service注解或者是不是漏了包扫描。相比之下debug报告在这种场景里提供不了太多增量信息。它会显示一些自动配置未生效但跟你这个Bean缺失问题关系不大。所以你不用每次遇到这个报错都开debug先看异常堆栈里有没有明确指向如果没有再让debug报告介入。4. 真实工作中高频踩中的隐性坑与场景实录说实话如果只是端口占用、DataSource没配置这种直白问题花不了几分钟就能解决。真正让人头疼的是那种“日志里能看出Bean创建失败但为什么失败又说不太清”的隐性原因。这几年帮人排查和自己在生产环境踩坑我总结出几类高频场景基本覆盖大多数Error starting ApplicationContext的来源。4.1 场景一Spring Boot版本升完配置类直接加载失败Spring Boot 3.0是个分水岭因为它把Java EE相关的包从javax.*迁移到了jakarta.*。如果你把一个原本基于Spring Boot 2.x的项目直接升到3.x但代码或第三方依赖还在用javax.servlet、javax.validation、javax.persistence这些包启动时往往会直接抛ClassNotFoundException或NoClassDefFoundError然后以Error starting ApplicationContext收场。我见过不少同学拿到一个Spring Boot 3项目本地装的却是JDK 8跑起来控制台报了一堆看不懂的错误。实际上Spring Boot 3.x要求JDK 17及以上版本不满足时启动都可能成问题。排查这种问题第一步先确认三件事JDK版本、Spring Boot版本、依赖里有没有明显的javax和jakarta混用。如果是从2.x往3.x升先全局搜代码里的javax.前缀能换jakarta.的换掉三方库不兼容的优先找替代方案或升级版本。4.2 场景二依赖冲突把自动配置类带偏Maven项目里依赖冲突太常见了冲突到不至于每次启动都会失败但一旦冲突触及Spring Boot自动配置的敏感类就会在启动阶段炸出来。我之前给一个项目排查过一个问题引入了A组件而A组件内部依赖了一个老版本的spring-tx结果把项目原本从Spring Boot里带的spring-tx版本给覆盖了。启动时报错大概是BeanFactory 里某个事务相关Bean初始化异常日志里还隐约能看到NoSuchMethodError。这个NoSuchMethodError十有八九就是版本冲突导致的某方法在新版本里有老版本里没有。排查方式很直接用maven命令看依赖树mvn dependency:tree -Dincludesorg.springframework:spring-tx找到版本冲突后在pom.xml里用dependencyManagement统一版本或者在有传递依赖的地方加exclusion排掉旧版本。这类问题用debug报告帮助不大因为报错发生在第三方配置类和Spring内部类交互过程中报告只会显示某个自动配置是否匹配不会显示依赖冲突细节。4.3 场景三Bean循环依赖启动到一半就崩如果你的项目用的是Spring Boot 2.6及以上版本并且代码里有循环依赖启动时很容易碰到这个报错Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | orderService ↑ ↓ | userService └─────┘ Action: Relying Upon Circular References is discouraged and by default prohibited.Spring Boot从2.6开始默认禁止循环依赖之前版本默认允许只是可能会打一条警告。很多老项目从2.5升到2.6或更高版本一启动就挂十有八九是这个原因。解决办法不是去设置spring.main.allow-circular-referencestrue把问题压下去。靠这个配置确实能启动但它只是把“内伤”藏起来后面在复杂业务里可能会触发更隐蔽的问题。正确做法是把循环依赖解开比如把某个互相调用的逻辑拆开或者用Lazy打破加载顺序。我在实际项目中更倾向于重构Service层的调用关系把公共逻辑抽到单独的组件里这样既解决循环依赖也让代码结构更清爽。4.4 场景四数据源和ORM框架初始化时机冲突这种场景多发于整合MyBatis和Spring Data JPA时。最开始项目只用MyBatis后来为了某个模块引入Spring Data JPA启动时突然报错日志里能看到同时有MyBatis的mapper扫描和JPA的实体扫描在处理同一个DataSource或者某个Repository初始化失败。根本原因往往是两个ORM框架都对DataSource实例做了二次包装或者自动配置顺序不对。这时候可以打开conditions report看MybatisAutoConfiguration和JpaAutoConfiguration到底谁生效了确认是不是两个同时生效。如果确实需要两个框架共存一般建议在配置里把一个自动配置排除掉只保留一个数据源关键配置的入口再分别配置Mapper扫描和EntityManager。另外还有一些情况是自定义的DataSource配置类跟自动配置的DataSource冲突。自己写了DataSourceConfig类用Bean创建了DataSource同时DataSourceAutoConfiguration也在生效启动过程可能因为初始化顺序先碰到自动配置报“DataSource的Bean已存在”之类的问题。解决办法是在启动类排除自动配置数据源SpringBootApplication(exclude {DataSourceAutoConfiguration.class})如果只做数据库基础配置可以不用手动建DataSource让Spring Boot自动装配接管反而少踩很多坑。4.5 场景五测试类、多模块下启动一半失败这类问题在本地开发时特别迷惑人。明明Application.java能正常启动一跑单元测试就报Error starting ApplicationContext。原因是SpringBootTest会加载整个Spring上下文如果测试环境没有准备数据库连接、缺少某个外部服务配置甚至测试类本身没找到主启动类都会启动失败。如果你用的是多模块工程还可能出现主模块在但测试模块扫描不到主类的情况。此时需要在测试类上指定主启动类SpringBootTest(classes DemoApplication.class)遇到测试启动失败先不要改测试逻辑看看它的加载上下文跟主程序差在哪。是不是配置类没扫到是不是某个profile没激活这些排查清楚比硬加mock注解强。5. 了解背后判断逻辑以后遇到提示不再慌看多了这种报错你会慢慢意识到想彻底搞定它光会照抄解决方案是不够的得理解Spring Boot启动时“自动配置”到底是怎么运作的这样你才能从一堆条件和匹配报告里找到真正有用的信息。5.1 自动装配在启动时到底干了什么Spring Boot的SpringBootApplication注解里藏着EnableAutoConfiguration它就像个总调度会把classpath下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里列出来的自动配置类挨个加载。但加载不等于全部生效。每个自动配置类上都会挂一堆ConditionalOnXxx之类的条件注解。比如RedisAutoConfiguration它会检查classpath里有没有RedisOperations类DataSourceAutoConfiguration会检查有没有DataSource相关的类。只有所有条件都满足配置类才会真正生效去创建对应的Bean。这就解释了为什么引入一个依赖才生效一个功能。你加了spring-boot-starter-data-redisclasspath里有了Redis客户端类RedisAutoConfiguration自动生效你没加它就安安静静躺在Negative matches里不产生任何影响。5.2 条件注解的判别机制简单拆解我整理了几个最常见的条件注解和它们的含义这样看报告时不至于一头雾水条件注解作用ConditionalOnClassclasspath中存在指定类时才生效ConditionalOnMissingClassclasspath中不存在指定类时才生效ConditionalOnBean容器中存在指定Bean时才生效ConditionalOnMissingBean容器中不存在指定Bean时才生效ConditionalOnProperty配置文件中存在指定配置项且值匹配时才生效ConditionalOnWebApplication当前应用是Web应用时才生效看到没有这类注解的语义都很直白。ConditionalOnMissingBean尤为重要这也是为什么你自定义了一个Bean之后Spring Boot的自动配置通常会退让不再创建同类型的默认Bean。但如果你自定义的配置类加载时机不对容器先被自动配置塞了一个同类型Bean再遇到你的定义就可能产生冲突。理解了这些机制再回看那份CONDITIONS EVALUATION REPORT你就会明白那些“Did not match”后面带的原因其实非常有信息量。它不是一堆无效日志而是一个完整的决策过程记录。5.3 用exclude控制不想要的自动配置有一种情况是你根本不想让某个自动配置生效比如项目只做非Web的定时任务但classpath里带了Web相关starter就会把Tomcat一起拉起来。这时可以在启动类上明确排除SpringBootApplication(exclude {DataSourceAutoConfiguration.class, MailSenderAutoConfiguration.class})也可以放在配置文件里不用改Java代码spring.autoconfigure.excludeorg.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration不过要谨慎使用。很多新手看到启动报错第一反应是排除某个自动配置类来“绕过”问题。比如明明没配数据源为了不报错就排除DataSourceAutoConfiguration结果后面用到数据库时又冒出一堆诡异问题。排除应该是主动选择而不是逃避报错的手段。6. 查错实用速查表与我的几点排查习惯最后把这些经验浓缩成一张速查表下次遇到类似报错可以直接对着看能少走很多弯路。6.1 常见启动失败报错信息与应对对照表报错关键词或片段优先排查方向常见对策Error starting ApplicationContext Caused by: BeanCreationException找到具体是哪个Bean创建失败再顺藤摸瓜看依赖检查Bean对应类的注解、构造参数、依赖是否齐全Failed to configure a DataSource数据源连接信息未配置补全spring.datasource.url、username、password、driver-class-nameThe Tomcat connector configured to listen on port 8080 failed to start端口被占用杀掉占用进程或改server.portThe dependencies of some of the beans ... form a cycleBean循环依赖重构代码尽量拆分循环调用ClassNotFoundException / NoClassDefFoundError依赖缺失或版本冲突检查maven依赖树统一或补充依赖Field xxx required a bean of type yyy that could not be found容器中没有指定Bean确认有没有包扫描、有没有加Component/Service等注解Invalid value type for attribute factoryBeanObjectTypeSpring和第三方库版本不兼容升级或对齐Spring Boot与各依赖版本Failed to determine a suitable driver class数据库驱动类没找到引入对应数据库驱动比如mysql-connector-j表格里的方向基本都是高频路径但具体某个项目可能还有自己的特殊情况核心思路还是不变先定位到具体的Bean和配置再找原因。6.2 几个高效的排查辅助手段除了开debug我再推荐几个很实用的辅助手段。第一个是使用Spring Boot Actuator的conditions端点。如果你已经加了spring-boot-starter-actuator依赖启动成功但运行期想在线查看自动配置条件可以在配置文件里把端点暴露出来然后访问/actuator/conditions旧版本是/actuator/conditions路径可能因版本不同略有差异会返回一份JSON格式的自动配置评估报告。这在排查“为什么某个配置在运行期没生效”时非常有用不用每次重启都开debug。第二个是“日志分段定位法”。把完整日志复制到文本编辑器先搜Error starting ApplicationContext看它前面的日志有没有关键信息再搜APPLICATION FAILED TO START看Description和Action最后逐个搜Caused by直到找到底层异常。这个方法其实不需要任何工具只靠一个良好的排查习惯就能解决很多看着复杂的问题。第三个是设置日志文件输出。在配置文件里加上logging.file.namelogs/app.log这样启动失败时控制台信息就算被截断也能到日志文件里找完整堆栈。IDEA控制台有时候会折叠长日志日志文件反而最完整。6.3 我的排错习惯我一直有个习惯项目里的任何一个依赖变更、版本调整、配置新增都有可能在启动阶段引入意外。所以每次升级Spring Boot版本或者引入新中间件时我第一件事不是写业务代码而是先启动一次空应用确保上下文能起来。这就像写代码之前先保证编译通过能省掉后面大量“不知道是代码问题还是配置问题”的纠结。另外一个习惯是看到“Error starting ApplicationContext”这种顶层信息时先不急着搜索而是花点时间把堆栈往下翻。Spring真的好几次救了我的命——它的Description和Action提示已经把答案写在脸上了只是因为太长被很多人忽略。如果你把这个报错当成一道线索题而不是一个诅咒学习和排查的速度都会快很多。排查这类问题最忌讳的就是“随机修改法”先改端口不行改数据库再不行排除某个配置。改来改去最后连自己动了什么都不知道。正确做法永远是先把完整信息捞到手面对报错一步一步找证据。等你多处理几次再看到这句“Error starting ApplicationContext”反而会觉得它是提醒你“注意看这里接下来就是破案现场”的提示音。

相关新闻

最新新闻

日新闻

周新闻

月新闻