Apr 26, 2022 9:32:29 PM org.apache.tomcat.util.scan.StandardJarScanner processURLs
WARNING: Failed to scan [file:/Users/xxx/.m2/repository/org/apache/derby/derby/10.12.1.1/derbyLocale_zh_TW.jar] from classloader hierarchy
java.io.FileNotFoundException: /Users/xxx/.m2/repository/org/apache/derby/derby/10.12.1.1/derbyLocale_zh_TW.jar (No such file or directory)
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.(ZipFile.java:225)
at java.util.zip.ZipFile.(ZipFile.java:155)
at java.util.jar.JarFile.(JarFile.java:167)
at java.util.jar.JarFile.(JarFile.java:131)
at org.apache.tomcat.util.compat.JreCompat.jarFileNewInstance(JreCompat.java:209)
at org.apache.tomcat.util.scan.JarFileUrlJar.(JarFileUrlJar.java:65)
at org.apache.tomcat.util.scan.JarFactory.newInstance(JarFactory.java:49)
at org.apache.tomcat.util.scan.StandardJarScanner.process(StandardJarScanner.java:387)
at org.apache.tomcat.util.scan.StandardJarScanner.processURLs(StandardJarScanner.java:322)
at org.apache.tomcat.util.scan.StandardJarScanner.doScanClassPath(StandardJarScanner.java:272)
at org.apache.tomcat.util.scan.StandardJarScanner.scan(StandardJarScanner.java:235)
at org.apache.catalina.startup.ContextConfig.processJarsForWebFragments(ContextConfig.java:1863)
at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1079)
at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:779)
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:299)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5130)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1427)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1417)
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)
具体报错代码行: 相关具体的代码行已经补充背景色
at org.apache.tomcat.util.scan.StandardJarScanner.process(StandardJarScanner.java:387)
at org.apache.tomcat.util.scan.StandardJarScanner.processURLs(StandardJarScanner.java:322)
at org.apache.tomcat.util.scan.StandardJarScanner.doScanClassPath(StandardJarScanner.java:272)
at org.apache.tomcat.util.scan.StandardJarScanner.scan(StandardJarScanner.java:235)
at org.apache.catalina.startup.ContextConfig.processJarsForWebFragments(ContextConfig.java:1863)
1. 在 debug 过程中,发现实际 tomcat 去扫描的入口为:org.apache.tomcat.util.scan.StandardJarScanner#doScanClassPath
1. 第一个addAll方法将找到的URL路径添加到classPathUrlsToProcess 这个链表当中。
2. 然后processURLs对队列当中的URL再进一步的处理。
3. 并且第一次循环的时候 Arrays.asList(((URLClassLoader) classLoader).getURLs()) 结果为空,第二次循环便有了 376 个值。 注意此处的不同项目依赖不同,总体的量也即 376 这个数字会不一样。
protected void doScanClassPath(JarScanType scanType, ServletContext context,
JarScannerCallback callback, Set processedURLs) {
...
Deque 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();
}
...
}
相关截图如下:
2. 查看进一步处理方法:org.apache.tomcat.util.scan.StandardJarScanner#processURLs
1. 注意到 所有的文件依赖路径也即 URL 都是从 classPathUrlsToProcess当中捞出
2. 从此处即可看出, tomcat 是要将对应的 刚才类加载扫出来的jar 包都要进行处理。3. 所以此处还并不是主要处理的地方,继续往下看。
protected void processURLs(JarScanType scanType, JarScannerCallback callback,
Set processedURLs, boolean isWebapp, Deque 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));
}
}
}
}
相关截图如下:
3. 查看进一步处理方法:org.apache.tomcat.util.scan.StandardJarScanner#process
1. 可以发现,每一次 jar 包都要被处理,也即一个 jar 包就是一个 URL 资源
2. 注意到,这个 process 是嵌套在一个上一步方法的循环当中,所以可以知道的是, try (Jar jar = JarFactory.newInstance(url)) 并不是抛出错误的地方,不然的话,怎么会有那么多的文件没找到呢!
3. 而isScanManifest()条件判断则是 Boolean 判断,不涉及相关具体处理逻辑,所以对应的额具体处理方法就在 processManifest(jar, isWebapp, classPathUrlsToProcess) 中
protected void process(JarScanType scanType, JarScannerCallback callback,
URL url, String webappPath, boolean isWebapp, Deque 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);
}
}
}
相关截图:
4. 查看进一步处理方法:org.apache.tomcat.util.scan.StandardJarScanner#processManifest
该方法里处理了几个重要的操作
1. 获取 jar 包的 "META-INF/MANIFEST.MF" 文件2. 获取 MANIFEST.MF 文件中的 Class-Path 属性
3. 获取当前 JAR 包路径
4. 将当前的 jar 包路径拼接好 刚才的第二步获取到的 Class-path 属性的 jar 包路径,并把最终的额结果再次塞回到了 classPathUrlsToProgress 链表中 这也是一直我们在外面查找那些凭空多出来的 derbyLocale 文件找不到相关依赖的原因在这。
相关截图:
查看对应的 多余的 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
相关截图:
可以得到,当前的多出这些多余的 jar 包路径,就是因为 scanManifest 属性 为 true,所以才进行了 扫描多余 jar 包步骤,并且拼接后的 路径在本地是没有的,所以就报错了。
1. 解压缩对应的 jar包,将对应的 class-path 属性删除掉(注意,这个前提是这个 jar 包依赖不影响打项目原有依赖),然后再打包回去
有比较大的问题在于:这个 jar 包是特殊的,除非编译源码重新修订,否则在当前的 所有公共仓库都有对应的 class-path,显然不能这样做
2. 如果找不到包,那就去下载这个对应的包去放在这个路径下
问题在于这个对应不需要的包越来越多情况下,会导致发包的文件大小更大,还有可能引入其他的 jar 包冲突,虽然本次debug 中没有
3. 通过对比查找相关资料,发现这个WARNING 是 tomcat 高版本导致的,也即恰巧是 8.5.1 以上的版本,所以降低 tomcat 版本也可以解决
4. 最正确的作法:;将对应的 org.apache.tomcat.util.scan.StandardJarScanner#scanManifest 的属性默认设置为 false 即可!
如何设置,请根据个人的所对应的项目进行适配,将scanManifest 属性变更为 false 即可了!