[转帖]一个空格导致应用启动失败的问题排查

一个,空格,导致,应用,启动,失败,问题,排查 · 浏览次数 : 0

小编点评

**问题分析** **问题** 我的Spring应用启动失败,因为我的spring.factories文件中多了一个空格。 **原因** 由于Spring版本不同,在旧版本的Spring中,如果引入的starter中的spring.factories中配置内容中包含空格,会导致解析失败,进而导致应用启动失败。 **解决方法** 1. 确保在旧版本的Spring中,如果引入的starter中的spring.factories中配置内容中包含空格,使用trim命令进行处理。 2. 仔细观察报错信息,不要忽略任何任何细节,包括空格。 3. 使用问题猜测,猜测后再验证。 4. 请用Google!参考资料进行搜索。 **其他** * 问题的解决过程中,有几个关键点,包括从报错信息本身入手,仔细看报错内容,不要忽略任何任何细节,包括空格。 * 在旧版本的Spring中,如果引入的starter中的spring.factories中配置内容中包含空格,会导致解析失败,进而导致应用启动失败。

正文

 

GitHub 24k Star 的Java工程师成神之路,不来了解一下吗!

先交代一下背景,在很久之前,我曾经封装过一个分库分表的扫表工具——Full Table Scanner,主要实现方式是通过使用TDDL Hint + 网格任务 + Mybatis Stream Query 提升性能,降低使用成本。

为了方便使用,我把他封装成了一个SpringBoot Starter,因为他提供了很好的快速扫表能力,所以被很多应用使用,并且一直都跑的好好的。

但是前两天,突然有人在钉钉上找我,说是他们应用做了改造,启动的时候报错,报错内容和我的这个工具有关。

主要报错信息如下:

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.taobao.pandora.boot.loader.MainMethodRunner.run(MainMethodRunner.java:54)
    at com.taobao.pandora.boot.loader.Launcher.launch(Launcher.java:87)
    at com.taobao.pandora.boot.loader.Launcher.launch(Launcher.java:50)
    at com.taobao.pandora.boot.loader.SarLauncher.main(SarLauncher.java:171)
    Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.alibaba.fin.xxx.test.CreditXXXStartUp]; nested exception is java.lang.IllegalStateException: Unable to read meta-data for class  com.alibaba.fin.table.scanner.autoconfiguration.TDDLTableTopologyBuilderAutoConfiguration
    at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:556)
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:185)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:308)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:228)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:272)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:92)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
    at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:134)
    at com.alibaba.fin.xxx.start.CreditpayXXXBootStartUp.main(CreditpayMercuryApplicationBootStartUp.java:88)...8 more
    Causedby: java.lang.IllegalStateException:Unable to read meta-data forclass  com.alibaba.fin.table.scanner.autoconfiguration.TDDLTableTopologyBuilderAutoConfiguration
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:217)
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationValue(AutoConfigurationSorter.java:198)
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.readBefore(AutoConfigurationSorter.java:186)
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.<init>(AutoConfigurationSorter.java:158)
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClasses.<init>(AutoConfigurationSorter.java:115)
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter.getInPriorityOrder(AutoConfigurationSorter.java:57)
    at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.sort(AutoConfigurationImportSelector.java:241)
    at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.selectImports(AutoConfigurationImportSelector.java:98)
    at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:547)...20 more
    Causedby: java.io.FileNotFoundException:class path resource [ com/alibaba/fin/table/scanner/autoconfiguration/TDDLTableTopologyBuilderAutoConfiguration.class] cannot be opened because it does not exist
    at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:172)
    at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:50)
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:98)
    at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.createMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:89)
    at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.getMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:76)
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:93)
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:213)...28 more

于是我开始帮忙排查,错误信息很长,其中最关键的信息就是这句:

Caused by: java.io.FileNotFoundException: class path resource [ com/alibaba/fin/table/scanner/autoconfiguration/TDDLTableTopologyBuilderAutoConfiguration.class] cannot be opened because it does not exist

看到这个报错,最开始的反应以为是jar包冲突了。

开始进行maven的依赖分析,发现并没有什么冲突的。

为了定位问题到底和这个Starter是否有关,我们先把关于这个工具的依赖排除掉了,之后就可以正常启动了。

基本确定一定是和这个工具的引入有关。于是开始排查这个包到底做了什么会导致应用启动失败。

排查的细节就不多说了,最终经过多方排查,我们最终发现这个问题竟然和一个空格有关。

仔细看上面的报错信息,提示找不到[ com/alibaba/fin/table/scanner/autoconfiguration/TDDLTableTopologyBuilderAutoConfiguration.class],有没有发现,在类前面多了个空格?

后来发现,错误信息中下面这句也更为关键:

Caused by: java.lang.IllegalStateException: Unable to read meta-data for class  com.alibaba.fin.table.scanner.autoconfiguration.TDDLTableTopologyBuilderAutoConfiguration

仔细观察发现,com.alibaba.fin.table.scanner.autoconfiguration.TDDLTableTopologyBuilderAutoConfiguration类前面也多了个空格?

大家都知道,应用在启动的时候,会扫描classpath下面的所有spring.factories文件,并加载其中的自动配置信息。如果在加载初始化的时候遇到什么特殊的情况,应用就会启动失败。

而上面报错的TDDLTableTopologyBuilderAutoConfiguration正是我的starter的自动配置文件。

于是打开我的spring.factories,发现在TDDLTableTopologyBuilderAutoConfiguration的配置前面,真的就多了一个空格:

为了快速定位是不是这个问题,我先把这个空格删除,然后很快帮同事打了一个SNAPSHOT的包。

同事引入了我的新包之后,重启应用,发现问题解决了。

到这个地方,基本定位到了问题,就是因为我的spring.factories文件中多了一个空格,导致应用启动失败了。

但是,为什么这个问题之前没有发现呢?这次这位同事又是改了什么东西导致问题出现了呢?

问题原因分析

根据报错内容的堆栈,我们先定位到org.springframework.core.io.ClassPathResource.getInputStream这个类在读取文件的时候失败了。

这个类是Spring中的一个文件读取的相关类。首先第一个想到是不是和Spring的版本有关。

于是我们快速到启动失败的应用的日常环境和线上环境分别看了一下打包之后的Spring的版本。

日常启动失败的机器中,打包后引用的Spring版本为:

$ls|grep spring-core
spring-core-4.3.13.RELEASE.jar

线上正常运行的机器中,打包后引用的Spring版本为:

$ls|grep spring-core
spring-core-4.3.22.RELEASE.jar

果然,这货是因为Spring的版本不同,和这位同学沟通后,得知他是改动了应用里面的一个包的依赖关系,那肯定是因为这个,导致了他的Spring版本仲裁关系发生了改变,从4.3.22.RELEASE降到了4.3.13.RELEASE。

先让这位同学去排查他的Spring仲裁的问题了,我继续分析,因为Spring版本不同,并且高版本没有这个问题,说明可能是一个Spring的bug,于是我尝试求助Google。

在这之前,同事的电脑浏览器默认是百度,我先用百度查询过,但是没查到,于是改用Google。

使用关键词:”spring.factories space trim“(这几个关键词很关键,spring.factories space是问题的根源,而且我觉得这一定是个被修复的bug,而且修复方式是用了trim)

很快就找到了一个相关内容,早在2018年有网友反馈过这个问题:

-w1034

在这个ISSUE中,有官方人员给出了回复,这个问题在SPR-17413(https://jira.spring.io/browse/SPR-17413?redirect=false )中被修复:

-w1021

其中提到,在4.3.21, 5.0.11, 5.1.2等版本中被修复,而我们的现象也确实是,线上正常运营的版本使用的是4.3.22。

顺藤摸瓜,查看一下Spring关于本次Bug修复的提交记录:

-w983

-w999

并且在他们的测试使用的spring-factories中也增加了空格,用于测试。

-w995

上面只是截图了一部分,其实这次bug修复,官方共提交了3次,涉及到43行代码的改动。他们在多处相关操作的地方,做了trim。

总结

本次问题的发生主要是因为在旧版本的Spring中,如果引入的starter中的spring.factories中配置内容中包含空格,会导致解析失败,进而导致应用启动失败。

这是一个已经被报告出来,并且也被解决了的bug。在SPR-17413中已经被修复,在高于4.3.21的版本中被修复。

其实,这篇文章的内容并不复杂,只是介绍了一个Bug,本来只是记录一下,并没什么可讲的。

但是,这个问题的排查过程还是值得学习的,有这样几个关键点:

  • 1、从报错信息本身入手。
  • 2、仔细看报错内容,不要忽略任何一个细节,哪怕是一个空格。
  • 3、学会问题猜测,猜测后再验证。
  • 4、请用Google!

参考资料:

https://github.com/spring-projects/spring-framework/issues/21946

https://github.com/spring-projects/spring-boot/issues/14903

https://jira.spring.io/browse/SPR-17413?redirect=false

与[转帖]一个空格导致应用启动失败的问题排查相似的内容:

[转帖]一个空格导致应用启动失败的问题排查

2021-02-03 分类:Java / spring 阅读(2930) 评论(2) GitHub 24k Star 的Java工程师成神之路,不来了解一下吗! 先交代一下背景,在很久之前,我曾经封装过一个分库分表的扫表工具——Full Table Scanner,主要实现方式是通过使用TDDL H

[转帖]Oracle如何重启mmon/mmnl进程(AWR自动采集)

https://www.cnblogs.com/jyzhao/p/10119854.html 学习一下 环境:Oracle 11.2.0.4 RAC现象:sysaux空间满导致无法正常生成快照,清理空间后,手工生成快照可以成功,但是观察自动生成快照依然是不成功。之前了解到awr对应的相关后台进程是m

[转帖]金仓数据库KingbaseES V8R6 索引膨胀

索引膨胀 对于索引,随着业务不断的增删改,会造成膨胀,尤其Btree索引,也会涉及索引分裂、合并等,导致索引访问效率降低、维护成本增加。另外,索引页的复用与HEAP PAGE不一样,因为索引的内容是有序结构,只有符合顺序的ITEM才能插入对应的PAGE中,不像HEAP TUPLE,只要有空间就可以插

[转帖]7.5 TiKV 磁盘空间占用与回收常见问题

https://book.tidb.io/session4/chapter7/compact.html TiKV 作为 TiDB 的存储节点,用户通过 SQL 导入或更改的所有数据都存储在 TiKV。这里整理了一些关于 TiKV 空间占用的常见问题 TiKV 的空间放大 监控上显示的 Number

[转帖]Windows sc 命令

语法# sc [] config [] [optionname= optionvalues] 注意 每个命令行选项 (参数) 必须包含等号作为选项名称的一部分。 选项及其值之间需要一个空格 (例如 ,type= own。 如果省略空格,操作将失败)。

[转帖]java -d 参数(系统属性) 和 环境变量

https://www.cnblogs.com/limeiyang/p/16565920.html 1. -d 参数说明 通过 java -h 查看可知: 注意:-D= : set a system property 设置系统属性。如果value是一个包含空格的字符串,则必须将该字符串括在双引号中。

[转帖]【VIM】多行缩进空格与删除

向前或向后缩进一个TAB 按ctrl + v组合键进入Visual Line模式,可使用方向键选择多行; 按<或>,进行向前或向后缩进tab。 缩进n个TAB,按n+<或> 多行缩进n个空格 向前缩进,实则是使用Visual Block模式删除多余空格以达到向前缩进的效果。 按ctrl + v组合键

[转帖]shell 把以空格分隔的变量 分割后的每个字段赋值给变量

比如我有一个变量 “123 456 789”,要求以空格为分隔符把这个变量分隔,并把分隔后的字段分别赋值给变量,即a=123;b=456;c=789 共有3中方法: 法一:先定义一个数组,然后把分隔出来的字段赋值给数组中的每一个元素 法二:通过eval+赋值的方式 法三:通过多次awk把每个字段赋值

[转帖]linux shell 中数组的定义和for循环遍历的方法

https://www.cnblogs.com/ysk123/p/11510718.html linux 中定义一个数据的语法为: variable=(arg1 arg2 arg3 ....) 中间用空格分开。数组的下标从0开始。 1 获取下标为n的元素: variable[n] 而且不存在数组下标

[转帖]python字符串如何删除后几位

https://www.python51.com/jc/15070.html 1、首先在jupyter notebook中新建一个空白的python文件: 2、然后定义一个字符串,用字符串截取的方式打印出排除最后三个字符的结果,这里的“s[:-3]”的意思就是从字符串取第0个字符至倒数第三个字符的前