【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx

Details In Errors

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)

How to resolve

  1. google 了非常多,但是都没能找到个精确的答案和回答
    1. 多数回答需要用户禁用 scanManifest,但是为什么?以及这些文件包哪里来的不知道
    2. 查询了这些包的依赖,通过 mvn dependency 查看,并未找到,说明并不是实际引入依赖进来的。
    3. 尝试设置了,但是结果无效,实际设置有问题,后面会有回答
  2. 最终通过了 debug tomcat 源码发现了点点端倪

具体报错代码行: 相关具体的代码行已经补充背景色

	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)

具体的代码 debug 过程 

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();
        }

        ...
    }

相关截图如下:

【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx_第1张图片

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));
          }
      }
  }
}

 相关截图如下:

【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx_第2张图片

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);
            }
        }
    }

 相关截图:

【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx_第3张图片

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 文件找不到相关依赖的原因在这。 

相关截图:

【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx_第4张图片

查看对应的 多余的 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 

相关截图:

【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx_第5张图片

综上所述

可以得到,当前的多出这些多余的 jar 包路径,就是因为 scanManifest 属性 为 true,所以才进行了 扫描多余 jar 包步骤,并且拼接后的 路径在本地是没有的,所以就报错了。 

【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx_第6张图片

解决办法 

 1. 解压缩对应的 jar包,将对应的 class-path 属性删除掉(注意,这个前提是这个 jar 包依赖不影响打项目原有依赖),然后再打包回去

有比较大的问题在于:这个 jar 包是特殊的,除非编译源码重新修订,否则在当前的 所有公共仓库都有对应的 class-path,显然不能这样做

2. 如果找不到包,那就去下载这个对应的包去放在这个路径下

问题在于这个对应不需要的包越来越多情况下,会导致发包的文件大小更大,还有可能引入其他的 jar 包冲突,虽然本次debug 中没有 

3. 通过对比查找相关资料,发现这个WARNING 是 tomcat 高版本导致的,也即恰巧是 8.5.1 以上的版本,所以降低 tomcat 版本也可以解决

【Tomcat】启动 tomcat,发现终端有非常多的 WARNING: Failed to scan xxx, java.io.FileNotFoundException: xxx_第7张图片

4. 最正确的作法:;将对应的 org.apache.tomcat.util.scan.StandardJarScanner#scanManifest 的属性默认设置为 false 即可! 

如何设置,请根据个人的所对应的项目进行适配,将scanManifest 属性变更为 false 即可了!

你可能感兴趣的:(java,tomcat,开发语言)