接上文 ,这是一个完全tomcat 启动的warning
2023-09-27 20:44:45 startup_process.py[line:172] INFO 27-Sep-2023 20:44:45.182 WARNING [main] org.apache.tomcat.util.scan.StandardJarScanner.processURLs Failed to scan [file:/ebay/app/Tomcat/lib/._tomcat-util-scan.jar] from classloader hierarchy
2023-09-27 20:44:45 startup_process.py[line:172] INFO java.util.zip.ZipException: error in opening zip file
2023-09-27 20:44:45 startup_process.py[line:172] INFO at java.util.zip.ZipFile.open(Native Method)
2023-09-27 20:44:45 startup_process.py[line:172] INFO at java.util.zip.ZipFile.<init>(ZipFile.java:233)
2023-09-27 20:44:45 startup_process.py[line:172] INFO at java.util.zip.ZipFile.<init>(ZipFile.java:162)
2023-09-27 20:44:45 startup_process.py[line:172] INFO at java.util.jar.JarFile.<init>(JarFile.java:173)
2023-09-27 20:44:45 startup_process.py[line:172] INFO at java.util.jar.JarFile.<init>(JarFile.java:137)
2023-09-27 20:44:45 startup_process.py[line:172] INFO at org.apache.tomcat.util.compat.JreCompat.jarFileNewInstance(JreCompat.java:256)
2023-09-27 20:44:45 startup_process.py[line:172] INFO at org.apache.tomcat.util.scan.JarFileUrlJar.<init>(JarFileUrlJar.java:65)
这是 embeded tomcat 的warning
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)
原因分析
其实问题就是出Manifest文件中的classpath,通过分析代码我们知道tomcat除了加载了我们maven管理的jar包之外,还会对jar中的manifest文件进行分析,如果其中存在classpath,他会将其中的内容也添加jar包依赖中,并对这些jar包进行加载。
解决方案
方案一:
删除Manifest中的classpath或者删除Manifest文件,这样就避免了加载不存在的jar包。但是每次maven更新的时候可能会覆盖掉你的修改,导致异常再次出现。
方案二:
按照加载提示的路径,将对应jar包复制过去并改名去掉版本号,但这样会造成jar冗余,同样的jar会加载两个。
方案三:
通过对比查找相关资料,发现这个WARNING 是 tomcat 高版本导致的,也即恰巧是 8.5.1 以上的版本,所以降低 tomcat 版本也可以解决。降级tomcat版本,使用8.5.0 或以下版本。8.5.0版本中不会对manifest进行分析加载,这样也就不会出现我们的异常了。
方案四
增加一下代码设置不扫描Manifest文件。将对应的 org.apache.tomcat.util.scan.StandardJarScanner#scanManifest 的属性默认设置为 false 即可!
细节请看上文。
具体的代码 debug 过程
-
在 debug 过程中,发现实际 tomcat 去扫描的入口为:org.apache.tomcat.util.scan.StandardJarScanner#doScanClassPath
-
第一个addAll方法将找到的URL路径添加到classPathUrlsToProcess 这个链表当中。
-
然后processURLs对队列当中的URL再进一步的处理。
-
并且第一次循环的时候 Arrays.asList(((URLClassLoader) classLoader).getURLs()) 结果为空,第二次循环便有了 376 个值。 注意此处的不同项目依赖不同,总体的量也即 376 这个数字会不一样。
protected void doScanClassPath(JarScanType scanType, ServletContext context,
JarScannerCallback callback, Set<URL> processedURLs) {
...
Deque<URL> classPathUrlsToProcess = new LinkedList<>();
while (classLoader != null && classLoader != stopLoader) {
if (classLoader instanceof URLClassLoader) {
if (isWebapp) {
isWebapp = isWebappClassLoader(classLoader);
}
classPathUrlsToProcess.addAll(
Arrays.asList(((URLClassLoader) classLoader).getURLs()));
processURLs(scanType, callback, processedURLs, isWebapp, classPathUrlsToProcess);
}
classLoader = classLoader.getParent();
}
...
}
相关截图如下:
![](https://img.haomeiwen.com/i26273155/67ced7d3d38f264e.png)
-
查看进一步处理方法:org.apache.tomcat.util.scan.StandardJarScanner#processURLs
-
注意到 所有的文件依赖路径也即 URL 都是从 classPathUrlsToProcess当中捞出
-
从此处即可看出, tomcat 是要将对应的 刚才类加载扫出来的jar 包都要进行处理。
-
所以此处还并不是主要处理的地方,继续往下看。
protected void processURLs(JarScanType scanType, JarScannerCallback callback,
Set<URL> processedURLs, boolean isWebapp, Deque<URL> classPathUrlsToProcess) {
...
while (!classPathUrlsToProcess.isEmpty()) {
URL url = classPathUrlsToProcess.pop();
if (processedURLs.contains(url)) {
// Skip this URL it has already been processed
continue;
}
ClassPathEntry cpe = new ClassPathEntry(url);
// JARs are scanned unless the filter says not to.
// Directories are scanned for pluggability scans or
// if scanAllDirectories is enabled unless the
// filter says not to.
if ((cpe.isJar() ||
scanType == JarScanType.PLUGGABILITY ||
isScanAllDirectories()) &&
getJarScanFilter().check(scanType,
cpe.getName())) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("jarScan.classloaderJarScan", url));
}
try {
processedURLs.add(url);
process(scanType, callback, url, null, isWebapp, classPathUrlsToProcess);
} catch (IOException ioe) {
log.warn(sm.getString("jarScan.classloaderFail", url), ioe);
}
} else {
// JAR / directory has been skipped
if (log.isTraceEnabled()) {
log.trace(sm.getString("jarScan.classloaderJarNoScan", url));
}
}
}
}
相关截图如下:
![](https://img.haomeiwen.com/i26273155/abee2f630142a64c.png)
-
查看进一步处理方法:org.apache.tomcat.util.scan.StandardJarScanner#process
-
可以发现,每一次 jar 包都要被处理,也即一个 jar 包就是一个 URL 资源
-
注意到,这个 process 是嵌套在一个上一步方法的循环当中,所以可以知道的是, try (Jar jar = JarFactory.newInstance(url)) 并不是抛出错误的地方,不然的话,怎么会有那么多的文件没找到呢!
-
而isScanManifest()条件判断则是 Boolean 判断,不涉及相关具体处理逻辑,所以对应的额具体处理方法就在 processManifest(jar, isWebapp, classPathUrlsToProcess) 中
protected void process(JarScanType scanType, JarScannerCallback callback,
URL url, String webappPath, boolean isWebapp, Deque<URL> classPathUrlsToProcess)
throws IOException {
if (log.isTraceEnabled()) {
log.trace(sm.getString("jarScan.jarUrlStart", url));
}
if ("jar".equals(url.getProtocol()) || url.getPath().endsWith(Constants.JAR_EXT)) {
try (Jar jar = JarFactory.newInstance(url)) {
if (isScanManifest()) {
processManifest(jar, isWebapp, classPathUrlsToProcess);
}
callback.scan(jar, webappPath, isWebapp);
}
} else if ("file".equals(url.getProtocol())) {
File f;
try {
f = new File(url.toURI());
if (f.isFile() && isScanAllFiles()) {
// Treat this file as a JAR
URL jarURL = UriUtil.buildJarUrl(f);
try (Jar jar = JarFactory.newInstance(jarURL)) {
if (isScanManifest()) {
processManifest(jar, isWebapp, classPathUrlsToProcess);
}
callback.scan(jar, webappPath, isWebapp);
}
} else if (f.isDirectory()) {
if (scanType == JarScanType.PLUGGABILITY) {
callback.scan(f, webappPath, isWebapp);
} else {
File metainf = new File(f.getAbsoluteFile() + File.separator + "META-INF");
if (metainf.isDirectory()) {
callback.scan(f, webappPath, isWebapp);
}
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// Wrap the exception and re-throw
throw new IOException(t);
}
}
}
相关截图:
- 查看进一步处理方法:org.apache.tomcat.util.scan.StandardJarScanner#processManifest
该方法里处理了几个重要的操作
- 获取 jar 包的 "META-INF/MANIFEST.MF" 文件
- 获取 MANIFEST.MF 文件中的 Class-Path 属性
- 获取当前 JAR 包路径
- 将当前的 jar 包路径拼接好 刚才的第二步获取到的 Class-path 属性的 jar 包路径,并把最终的额结果再次塞回到了 classPathUrlsToProgress 链表中 这也是一直我们在外面查找那些凭空多出来的 derbyLocale 文件找不到相关依赖的原因在这。
相关截图:
![](https://img.haomeiwen.com/i26273155/0fa39d548417129d.png)
![](https://img.haomeiwen.com/i26273155/2261306c9483a479.png)
查看对应的 多余的 derby 的 MANIFEST.MF 文件,可以发现如下内容: Class-Path: derbyLocale_cs.jar derbyLocale_de_DE.jar derbyLocale_es.ja r derbyLocale_fr.jar derbyLocale_hu.jar derbyLocale_it.jar derbyLocal e_ja_JP.jar derbyLocale_ko_KR.jar derbyLocale_pl.jar derbyLocale_pt_B R.jar derbyLocale_ru.jar derbyLocale_zh_CN.jar derbyLocale_zh_TW.jar
相关截图:
![](https://img.haomeiwen.com/i26273155/3c2268176fece705.png)
综上所述
可以得到,当前的多出这些多余的 jar 包路径,就是因为 scanManifest 属性 为 true,所以才进行了 扫描多余 jar 包步骤,并且拼接后的 路径在本地是没有的,所以就报错了。
![](https://img.haomeiwen.com/i26273155/07db7766eed52655.png)
网友评论