现象描述:
项目使用 springboot 启动一个 web 项目, 在启动阶段看到 console 中出现了异常 "1.10.3-1.4.3\hdf5.jar 系统找不到指定的文件", 虽然这些异常不影响项目的正常运行, 但作为一个严谨的技术人员, 看到这些异常就像见到仇人一样, 一定要除之而后快.
- java.io.FileNotFoundException: D:\.m2\repository\org\bytedeco\javacpp-presets\hdf5-platform\1.10.3-1.4.3\hdf5.jar (系统找不到指定的文件.)
- at java.util.zip.ZipFile.open(Native Method)
- at java.util.zip.ZipFile.<init>(ZipFile.java:225)
- at java.util.zip.ZipFile.<init>(ZipFile.java:155)
- at java.util.jar.JarFile.<init>(JarFile.java:166)
- at java.util.jar.JarFile.<init>(JarFile.java:130)
- at org.apache.tomcat.util.compat.JreCompat.jarFileNewInstance(JreCompat.java:188)
- 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)
- at org.apache.jasper.servlet.TldScanner.scanJars(TldScanner.java:262)
- at org.apache.jasper.servlet.TldScanner.scan(TldScanner.java:104)
- at org.apache.jasper.servlet.JasperInitializer.onStartup(JasperInitializer.java:101)
- at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5204)
- at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
- at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1421)
- at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1411)
- at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
- at java.util.concurrent.FutureTask.run(FutureTask.java)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
- at java.lang.Thread.run(Thread.java:748)
- 2019-03-29 18:09:08.303 WARN 16940 --- [ost-startStop-1] o.a.tomcat.util.scan.StandardJarScanner : Failed to scan [file:/D:/.m2/repository/org/bytedeco/javacpp-presets/hdf5-platform/1.10.3-1.4.3/hdf5-Linux-x86.jar] from classloader hierarchy
- java.io.FileNotFoundException: D:\.m2\repository\org\bytedeco\javacpp-presets\hdf5-platform\1.10.3-1.4.3\hdf5-Linux-x86.jar (系统找不到指定的文件.)
- ......
- 2019-03-29 18:09:08.578 WARN 16940 --- [ost-startStop-1] o.a.tomcat.util.scan.StandardJarScanner : Failed to scan [file:/D:/.m2/repository/org/bytedeco/javacpp-presets/hdf5-platform/1.10.3-1.4.3/hdf5-Linux-x86_64.jar] from classloader hierarchy
- java.io.FileNotFoundException: D:\.m2\repository\org\bytedeco\javacpp-presets\hdf5-platform\1.10.3-1.4.3\hdf5-Linux-x86_64.jar (系统找不到指定的文件.)
项目环境说明
tomcat: 使用 springboot 内置版本 8.5.29
使用 Maven 进行依赖管理
spring boot 版本为 2.0.1
spring 框架 版本为 5.0.5
项目引用了 Deep Learn 4 Java(一个非常棒的 Java 的机器学习库)
- <dependency>
- <groupId>org.deeplearning4j</groupId>
- <artifactId>deeplearning4j-core</artifactId>
- <version>1.0.0-beta3</version>
- </dependency>
有问题的 jar 依赖关系
image.PNG
跟踪分析
既然是在启动阶段报错, 那就找到启动类添加断点, 一步步跟踪下到底哪个阶段报的错误, 然后再分析出错的原因. 我跟踪调试了 springboot 的代码, 找到 jar 的加载位置. 主要的几个类和方法如下所示:
跟踪类 org.apache.tomcat.util.scan.StandardJarScanner
方法 doScanClassPath(...)
该方法会对所有 classloader 进行遍历, 加载每一个 classloader 中 jar 包
image.PNG
上图标红处就是关键代码, 其中变量 classPathUrlsToProcess 中存放的是所有待加载的 jar 信息, 主要是 jar 包路径信息, 我们可以看到这里面和我们在 maven 中看到的 jar 包是一样的.
image.PNG
方法 processURLs(...)
该方法会对当前 classloader 的所有 jar, 也就是对 classPathUrlsToProcess 进行堆栈操作, 然后处理每一个 jar 包. 关键代码如下所示.
image.PNG
方法 process()
该方法会对每一个 jar 进行加载及分析处理, 该方法中重点关注
processManifest(jar, isWebapp, classPathUrlsToProcess)
image.PNG
方法 processManifest
该方法会处理 jar 中的 Manifest 文件, 对 Manifest 文件中的 Class-Path 进行分隔处理, 对其中的内容作为新的依赖 jar 再插入到 classPathUrlsToProcess 中 (processURLs 方法会按照堆栈结果加载其中的 jar)
image.PNG
原因分析
其实问题就是出 Manifest 文件中的 classpath, 通过分析代码我们知道 tomcat 除了加载了我们 maven 管理的 jar 包之外, 还会对 jar 中的 manifest 文件进行分析, 如果其中存在 classpath, 他会将其中的内容也添加 jar 包依赖中, 并对这些 jar 包进行加载.
我们打开其中 hdf5-1.10.3-1.4.3.jar 的 manifest 文件作为例子看看错误出在哪里.
image.PNG
大家注意到了没有, 这里的 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 文件.
- @Bean
- public TomcatServletWebServerFactory tomcatFactory() {
- return new TomcatServletWebServerFactory() {
- @Override
- protected void postProcessContext(Context context) {
- ((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
- }
- };
- }
image.PNG
来源: http://www.jianshu.com/p/7ed2eaffbc18