为什么在 WEB-INF/lib 中更改 jar 文件的目录顺序会导致 Tomcat 8 中出现 NoClassDefFoundError?

Why would changing directory order of jar files in WEB-INF/lib cause a NoClassDefFoundError in Tomcat 8?

我们 运行 在 Tomcat 8 中有一个 Web 应用程序,最近我们观察到我们团队中的一些开发人员构建的工件(.war 文件)抛出 NoClassDefFoundError,而其他人构建的相同代码按预期运行。

来自logs/localhost.2018-05-11.log

org.jboss.resteasy.spi.UnhandledException: java.lang.NoClassDefFoundError: Could not initialize class org.geotools.referencing.datum.DefaultEllipsoid
    ...
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.geotools.referencing.datum.DefaultEllipsoid
    at org.geotools.referencing.GeodeticCalculator.<init>(GeodeticCalculator.java:277)
    ...

有时,但并非总是伴随(之前):

org.jboss.resteasy.spi.UnhandledException: java.lang.IncompatibleClassChangeError: Implementing class
    ...

检查 war 文件,工作工件和损坏工件的内容似乎相同,但有一个明显的例外,WEB-INF/lib 中的 jar 文件的 "directory ordering" 不同。

对展开的 war 文件执行以下过程并重新启动 Tomcat 似乎可以消除异常:

$ # jars in "bad" order
$ ls -U WEB-INF/lib
x.jar
b.jar
y.jar
a.jar
c.jar
z.jar
$ cp -p WEB-INF/lib/* /tmp/lib/
$ rm -r WEB-INF/lib
$ mv /tmp/lib WEB-INF/lib
$ # jars in "good" order (appears to be alphabetical after a 'cp' on my system)
$ ls -U WEB-INF/lib
a.jar
b.jar
c.jar
x.jar
y.jar
z.jar

"good" war 没有按字母顺序排列的罐子,但似乎有许多 "good" 个订单 "bad" 个订单.

我最初认为我们可能在不同的 jar 中有多个版本的 DefaultEllipsoid class,导致正确版本和另一个版本之间出现竞争条件,但事实似乎并非如此.

我在 tomcat 中启用了详细的 classloader 调试,在这两种情况下,logs/catalina.out 显示这个 class 是从正确的 jar 加载的:

[Loaded org.geotools.referencing.datum.DefaultEllipsoid from file: /opt/tomcat/temp/1-webapp/WEB-INF/lib/gt-referencing-11.4.jar]

知道这里会发生什么吗?

详情:

应 K. Sopheak 和 amod(版主)的要求,我更新一下。

此异常有一个原因,与 Tomcat 临时目录有关。

来自先前部署的临时文件 可能会导致此问题。检查 workDir(默认值:$TOMCAT_BASE/work),当 Tomcat 停止时,通过删除所有内容来清理它。

罐子的顺序真的很重要。 基本上 class 加载器就是这样加载它们的。 所以当同一个 class 有多个版本时,你可能会得到这个 NoClassdefFoundError 或 java.lang.IncompatibleClassChangeError: 它能够看到您 classes 但不知道要使用哪个,或者 classes 被修改并放置在多个 jar 中。检查你的罐子是否有任何重复的 classes 然后你可以消除根本原因。

针对类似问题 What causes IncompatibleClassChangeError 给出了几个有趣的答案,表明您和其他开发人员在编译时没有使用完全相同的 jar 文件。比较(至少是文件大小,但校验和会更好)用于编译的每个 jar 文件,以及在运行时使用的文件,当然还有您自己使用的文件和其他开发人员使用的文件。

行:

Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.geotools.referencing.datum.DefaultEllipsoid

表示找到class DefaultEllipsoid但有效,还有一些other class需要加载但是这失败了。另一个 class 无效。

可能是这个class与两个非常不同的版本重复,或者一个版本用于编译,另一个版本在运行时具有不同的方法签名。

此外,从 tomcat8 开始,WEB-INF/lib 中的应用程序 jar 文件 NOT 不再按字母顺序加载。我认为 tomcat 站点上有一个文档,但现在我找不到它了,但我在 tomcat bugzilla bug 57129

上发现了一个回归错误(不会修复)

这个 classloader 的东西意味着如果你在 WEB-INF/lib 上改变一些东西并重新启动 Tomcat,那么会有一些随机的 class 加载使得如果存在重复的 jar 版本,您的应用程序将以一种或另一种方式加载。

总结一下:检查 DefaultEllipsoid 导入并检查那些 class 是否有重复项。您的构建还需要清理以使用与运行时相同的版本(我希望您使用 maven 之类的工具来构建)