问题描述
项目使用springboot启动一个web项目,在启动阶段看到console中出现了异常“1.10.3-1.4.3\hdf5.jar 系统找不到指定的文件”,虽然这些异常不影响项目的正常运行。
项目环境说明
tomcat:使用springboot内置版本8.5.29
Maven依赖管理
springboot版本为2.0.1
spring框架版本为5.0.5
项目引用了Deep Learn 4Java(一个非常棒的Java的机器学习库)
有问题的jar依赖关系
跟踪分析
既然是在启动阶段报错,那就找到启动类添加断点,一步步跟踪下到底哪个阶段报的错误,然后再分析出错的原因。
主要的几个类和方法如下所示:
跟踪类org.apache.tomcat.util.scan.StandardJarScanner
方法doScanClassPath(...)
该方法会对所有classloader进行遍历,加载每一个classloader中jar包
上图标红处就是关键代码,其中变量classPathUrlsToProcess中存放的是所有待加载的jar包路径信息。
这跟maven外部库的jar是一样的
- 方法processURLs(...)
该方法会对当前classloader的所有jar,也就是对classPathUrlsToProcess进行堆栈操作,然后处理每一个jar包。关键代码如下所示。
- 方法process()
该方法会对每一个jar进行加载及分析处理,该方法中重点关注
processManifest(jar, isWebapp, classPathUrlsToProcess)
- 方法 processManifest
该方法会处理jar中的Manifest文件,对Manifest文件中的Class-Path进行分隔处理,对其中的内容作为新的依赖jar再插入到classPathUrlsToProcess中(processURLs方法会按照堆栈结果加载其中的jar)
原因分析
其实问题就是在Manifest文件中的classpath,通过分析代码我们知道tomcat除了加载了我们maven管理的jar包
之外,还会对jar中的manifest文件进行分析,如果其中存在classpath,它就会将其中的内容也添加到jar包依赖中,并对这些jar包进行加载。
我们打开其中hdf5-1.10.3-1.4.3.jar的manifest文件作为例子看看错误出在哪里。
大家注意到了没有,这里的jar包没有路径也没有版本号,这就导致tomcat加载的时候按照hdf5-1.10.3-1.4.3.jar的路径进行加载。
然而我们的工程中在对应位置并不存在这些jar,这也就导致了找不到jar的异常。我们工程中实际上有这些jar,只不过路径和名字不一样。在上图左边大家可以看到maven中其实已经有了这些jar,只不过名字后面多了版本号,路径在各自的maven仓库中。
到这里我们已经将出现问题的原因弄清楚了,接下来我们考虑下怎么解决。
解决方案
方案一:
删除Manifest中的classpath或者删除Manifest文件,这样就避免了加载不存在的jar包。但是每次maven更新的时候可能会覆盖掉你的修改,导致异常再次出现。
方案二:
按照加载提示的路径,将对应jar包复制过去并改名去掉版本号,但这样会造成jar冗余,同样的jar会加载两个。
方案三:
降级tomcat版本,使用8.5.0 或以下版本。8.5.0版本中不会对manifest进行分析加载,这样也就不会出现我们的异常了。
方案四
增加一下代码设置不扫描Manifest文件。