如何解决一个全网都找不到答案的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练练手吧。

目录
相关文章
|
8月前
|
安全 网络协议 Java
【紧急】Log4j又发新版2.17.0,只有彻底搞懂漏洞原因,才能以不变应万变,小白也能看懂
经过一周时间的Log4j2 RCE事件的发酵,事情也变也越来越复杂和有趣,就连 Log4j 官方紧急发布了 2.15.0 版本之后没有过多久,又发声明说 2.15.0 版本也没有完全解决问题,然后进而继续发布了 2.16.0 版本。大家都以为2.16.0是最终终结版本了,没想到才过多久又爆雷,Log4j 2.17.0横空出世。
224 0
|
8月前
|
测试技术
《游戏测试》经典BUG解析001--002
《游戏测试》经典BUG解析001--002
|
监控 安全 架构师
抱歉,你测试的项目上线之后bug太多了!
抱歉,你测试的项目上线之后bug太多了!
|
Java 中间件 程序员
最网最全bug定位套路,遇见bug再也不慌了
最网最全bug定位套路,遇见bug再也不慌了
241 0
|
测试技术
软件测试面试题:软件上线后有bug怎么处理?
软件测试面试题:软件上线后有bug怎么处理?
163 0
|
前端开发 计算机视觉 Python
代码报错还好说,源码报错才难搞!分享自己源码报错的解决过程!
代码报错还好说,源码报错才难搞!分享自己源码报错的解决过程!
87 0
代码报错还好说,源码报错才难搞!分享自己源码报错的解决过程!
|
缓存 NoSQL 关系型数据库
语音聊天系统,问题分析是找到解决方法的关键
语音聊天系统,问题分析是找到解决方法的关键
|
Arthas 监控 Java
看了这篇文章,比同事更快找到bug!
你以为程序员只是闷着头疯狂写bug,写好了发布到服务器就完了? 不,你还要修bug!但在那之前,你还要找bug!
170 0
写Bug时,需要注意的几点3
写Bug时,需要注意的几点3
101 0
|
算法
写Bug时,需要注意的几点 02
写Bug时,需要注意的几点 02
84 0