tomcat加载jar异常问题的分析与解决
现象描述:
项⽬使⽤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 at.utilpat.JreCompat.jarFileNewInstance(JreCompat.java:188)
at at.util.scan.JarFileUrlJar.<init>(JarFileUrlJar.java:65)
at at.util.wInstance(JarFactory.java:49)
at at.util.scan.StandardJarScanner.process(StandardJarScanner.java:374)
at at.util.scan.StandardJarScanner.processURLs(StandardJarScanner.java:309)
at at.util.scan.StandardJarScanner.doScanClassPath(StandardJarScanner.java:266)
at at.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.Startup(JasperInitializer.java:101)
at org.StandardContext.startInternal(StandardContext.java:5204)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.ContainerBase$StartChild.call(ContainerBase.java:1421)
at org.ContainerBase$StartChild.call(ContainerBase.java:1411)
at urrent.FutureTask.run$$$capture(FutureTask.java:266)
at urrent.FutureTask.run(FutureTask.java)
at urrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at urrent.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] at.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] at.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依赖关系
跟踪分析
既然是在启动阶段报错,那就到启动类添加断点,⼀步步跟踪下到底哪个阶段报的错误,然后再分析出错的原因。我跟踪调试了springboot的代码,到jar的加载位置。主要的⼏个类和⽅法如下所⽰:
跟踪类at.util.scan.StandardJarScanner
⽅法doScanClassPath(...)
该⽅法会对所有classloader进⾏遍历,加载每⼀个classloader中jar包
上图标红处就是关键代码,其中变量classPathUrlsToProcess中存放的是所有待加载的jar信息,主要是jar包路径信息,我们可以看到这⾥⾯和我们在maven中看到的jar包是⼀样的。
⽅法processURLs(...)
该⽅法会对当前classloader的所有jar,也就是对classPathUrlsToProcess进⾏堆栈操作,然后处理每⼀个jar包。关键代码如下所⽰。
⽅法process()
该⽅法会对每⼀个jar进⾏加载及分析处理,该⽅法中重点关注
processManifest(jar, isWebapp, classPathUrlsToProcess)
⽅法 processManifest
该⽅法会处理jar中的Manifest⽂件,对Manifest⽂件中的Class-Path进⾏分隔处理,对其中的内容作为新的依赖jar再插⼊到classPathUrlsToProcess中(processURLs⽅法会按照堆栈结果加载其中的jar)
springboot其实就是spring
原因分析
其实问题就是出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⽂件。
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
((StandardJarScanner) JarScanner()).setScanManifest(false);
}
};
}
总结:
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。