如何解决一个全网都找不到答案的bug?

简介: 如何解决一个全网都找不到答案的bug?

前言

程序员看到异常就好像看到天敌一样,要么解决掉,要么把它隐藏掉(苦笑~),哪怕有些异常并不影响业务,或只是警告信息。

最近就遇到一个启动Spring Boot项目时,抛出一堆包含Exception的警告信息,于是每天心心念念的事就是干掉它。

今天终于有时间来解决了,但发现几乎搜遍全网只找到类似的问题,却没有找到对应的解决方案。于是拿起debug利器,分析了一波源码,还真找到原因了。

或许你不会在项目中遇到类似的问题,但该问题的解决思路一定会对你有所启发,不信你读完该篇文章试试。

异常现状

启动Spring Boot项目时会抛出如下异常:










2020-10-27 19:20:33.706  WARN 13099 --- [           main] o.a.tomcat.util.scan.StandardJarScanner  : Failed to scan [file:/Users/zzs/.m2/repository/com/sun/xml/bind/jaxb-impl/2.1/jaxb-api.jar] from classloader hierarchy
java.io.FileNotFoundException: /Users/zzs/.m2/repository/com/sun/xml/bind/jaxb-impl/2.1/jaxb-api.jar (No such file or directory)
// ...
2020-10-27 19:20:33.708  WARN 13099 --- [           main] o.a.tomcat.util.scan.StandardJarScanner  : Failed to scan [file:/Users/zzs/.m2/repository/com/sun/xml/bind/jaxb-impl/2.1/activation.jar] from classloader hierarchy
java.io.FileNotFoundException: /Users/zzs/.m2/repository/com/sun/xml/bind/jaxb-impl/2.1/activation.jar (No such file or directory)

总之是一堆堆的异常信息,虽然日志级别为WARN,实在看不下去。

在网上也有大量的类似问题,比如:


[WARNING] [path] bad path element "/home/fprochazka/.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.2/jakarta.xml.bind-api-2.3.2.jar": no such file or directory

再比如:


Caused by: java.io.FileNotFoundException: /usr/local/tomcat/webapps/u-plan/WEB-INF/lib/activation-1.1.1.jar (No such file or directory)

等等,各类相关的问题。但统一的结果都是没准确的答案,或治标不治本,或临时解决但不知道最终原因的答案。

那么,我们就从根本上来分析一下导致问题的原因。

原因追踪

首先,从打印的堆栈(片段)信息来看:









at java.util.jar.JarFile.<init>(JarFile.java:130)  at org.apache.tomcat.util.compat.JreCompat.jarFileNewInstance(JreCompat.java:164)  at org.apache.tomcat.util.scan.JarFileUrlJar.<init>(JarFileUrlJar.java:65)  at org.apache.tomcat.util.scan.JarFactory.newInstance(JarFactory.java:49)  at org.apache.tomcat.util.scan.StandardJarScanner.process(StandardJarScanner.java:374)  at org.apache.tomcat.util.scan.StandardJarScanner.processURLs(StandardJarScanner.java:309)  at org.apache.tomcat.util.scan.StandardJarScanner.doScanClassPath(StandardJarScanner.java:266)  at org.apache.tomcat.util.scan.StandardJarScanner.scan(StandardJarScanner.java:229)

从上面我们可以看出,其实出问题就出在tomcat进行jar文件加载时找不到对应的文件目录。

下面亮出神器——debug。先把断点打在StandardJarScanner#doScanClassPath方法当中。image.png当浏览完整个方法,你会发现核心的代码就在此处两行,第一个addAll方法就是将找到的URL路径添加到Deque<URL>队列当中。然后processURLs对队列当中的URL再进一步的处理。

让项目启动起来,先看看获取到的URL都是什么。第一次循环时为空,第二次循环时便有值了。image.png我这里有160个jar文件,我们看jar文件的路径会发现就是JDK自身的jar包和pom文件中配置的依赖jar包。

也就是说,tomcat在启动时会把所需jar包进行扫描加载。那么单单就加载这160个jar吗?并不是,继续看看processURLs方法。image.pngprocessURLs方法会将队列当中的URL遍历处理,处理的结果放到processedURLs集合(Set)当中。看名字就知道是处理过的URL集合。

同时,还会调用一个process方法,重点来了,注意听讲。先看process方法核心实现:image.png看到什么了?也就是说如果URL对应的资源是一个jar文件,或者以.jar结尾,那么就通过JarFactory创建一个Jar对象。其实前面抛异常便是创建对象时,对应的jar不存在导致的。

但此时是第一层循环,所以不存在JarFactory创建抛异常的问题,问题在processManifest方法内。image.pngprocessManifest方法做了几个重要的操作:1、获取当前jar包的MANIFEST.MF文件内容;2、获取MANIFEST.MF文件中的Class-Path属性,并解析属性;3、获取前jar的路径;4、以当前jar包的路径拼接Class-Path中的jar包路径,并把结果放到前面提到的classPathUrlsToProcess队列中。

以项目中出现异常的jar包依赖为例:image.png为什么加载不到?注意上面我们说的processManifest方法的第3、4条,以当前jar包的路径来拼接Class-Path的jar包路径。

当前jar包路径为:com/sun/xml/bind/jaxb-impl,然后添加上jaxb-api.jar,路径变为com/sun/xml/bind/jaxb-impl/xx/jaxb-api.jar。其中中间的xx为版本号。

那么再看依赖文件中引入的jaxb-api.jar的路径应该是什么:/javax/xml/bind/xx/jaxb-api.jar。

此时当然找不到对应的jar包了,路径完全错了嘛。

原因汇总

其实整个异常的问题就出在tomcat加载classpath中的jar包时,把jar包中Manifest文件中配置的Class-Path的jar包也给加载了。

加载倒是没问题,但它加载时还把路径给拼错了,坑不坑?

由于此处的拼接,会出现多种错误:要么路径直接错了;要么路径上没有版本号;要么jar包上没有版本号(即文件名错误)……

解决方案

既然找到问题了,那么就是思考如何解决了。

第一种方案:既然MANIFEST.MF文件中指定的jar包加载错误并不影响程序运行,那就把它删掉吧。但遇到像本例中依赖的是aliyun的jar包,那就有点坑了。除非自己改它的代码,并上传的一个私服上。并不太可行,而且还会出现不知情的其他错误。

第二种方案:既然知道它要找那个路径了,那就在对应的路径上放置对应的jar包不就可以了?其实也有问题,因为其他依赖已经引入了jar包,而再通过其他路径引入一份,双方jar包存在,不仅增加的了发布文件的大小,还会引起jar包冲突的问题。

第三种方案:此异常是由于tomcat高版本导致的,使用8.5.0 或以下版本,也可以规避此问题。

第四种方案:指定不扫描Manifest文件。以Spring Boot为例,在启动类中配置如下配置:

@Bean
public TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {
        @Override
        protected void postProcessContext(Context context) {
            ((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
        }
    };
}

等于是重新构建了一个TomcatServletWebServerFactory,在构建的过程中指定不扫描Manifest文件。但此种方案,也可能会有隐患,就是如果Manifest文件中的路径万一正确了呢!

哈哈,其实别痴心妄想了~看上面的拼接方式正确的概率不会太大。

其实呢,还有第五种方案,只要你能忍受,那么就看着WARN警告吧,就当什么都看不到……

小结一下

通过一路debug,一路狂分析,终于找到问题原因。那么解决问题,其实就很简单了。

读到这里最起码你再遇到该问题时,已经知道基本的解决思路了。

但你get到如何解决一个全网都很难找到答案的bug了吗?赶紧找个bug练练手吧。

目录
相关文章
|
SQL HIVE
【Hive SQL 每日一题】环比增长率、环比增长率、复合增长率
该文介绍了环比增长率、同比增长率和复合增长率的概念及计算公式,并提供了SQL代码示例来计算商品的月度增长率。环比增长率是相邻两期数据的增长率,同比增长率是与去年同期相比的增长率,复合增长率则是连续时间段内平均增长的速率。文章还包含了一组销售数据用于演示如何运用这些增长率进行计算。
1537 4
|
存储 安全 Java
OAuth2实现单点登录SSO完整教程,其实不难!(上)
OAuth2实现单点登录SSO完整教程,其实不难!
4831 1
OAuth2实现单点登录SSO完整教程,其实不难!(上)
|
2月前
|
SQL 关系型数据库 MySQL
一张5000万行的表,加索引从45秒到0.02秒——索引设计你真的会吗
本文实测5000万订单表:无索引查询45秒,加索引后仅0.02秒(提升2250倍)。详解索引原理、建索引时机、联合索引最左前缀、覆盖索引及隐式转换陷阱,干货不啰嗦!
|
Java Linux 开发工具
linux如何编译安装源码包软件
源码包软件是包含源代码的软件包,常见于Linux和BSD系统,如fcitx和Lumaqq。开源软件需满足源代码可见且有宽松许可,如GPL。安装源码包通常涉及解压、编译和安装步骤,使用`tar`命令解包,通过`./configure`, `make`, `make install`进行编译安装。软件源码允许定制和二次开发,但须遵循许可协议。安装前确保安装了必要的开发工具和库。对于perl和python程序,直接使用perl或python命令安装。推荐使用系统软件包管理工具如RPM、yum或apt进行管理,以简化流程。
730 0
|
前端开发 对象存储
基于RuoYi-Flowable-Plus的ruoyi-nbcio项目的formdesigner文件上传与回显处理
基于RuoYi-Flowable-Plus的ruoyi-nbcio项目的formdesigner文件上传与回显处理
429 0
|
前端开发 API
前端界面生成PDF并导出下载
【10月更文挑战第21天】利用合适的第三方库,你可以在前端轻松实现界面生成 PDF 并导出下载的功能,为用户提供更方便的文档分享和保存方式。你还可以根据具体的需求进一步优化和定制生成的 PDF 文件,以满足不同的业务场景要求。
|
Android开发
[√]Android 通过adb内存监测方法
[√]Android 通过adb内存监测方法
1137 1
|
安全 Go
阿萨聊测试 ZAP5: 定制扫描策略
阿萨聊测试 ZAP5: 定制扫描策略
590 0
|
数据库 数据库管理
sqlite数据库文件导入到sqlserver
sqlite数据库文件导入到sqlserver