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

7
我们有一个运行在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文件的“目录排序”不同。

在爆炸的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

"好的"战争没有按字母顺序排序,但似乎有一些"好的"命令和一些"坏的"命令。
起初我以为我们可能有不同jar包中多个版本的DefaultEllipsoid类,导致正确版本和另一个版本之间存在竞争条件,但事实并非如此。
我在tomcat中启用了详细的类加载器调试,在两种情况下,logs/catalina.out都显示该类从正确的jar包中加载:
[Loaded org.geotools.referencing.datum.DefaultEllipsoid from file: /opt/tomcat/temp/1-webapp/WEB-INF/lib/gt-referencing-11.4.jar]

这里发生了什么事情有什么想法吗?

细节:

  • CentOS 7
  • Apache Tomcat/8.0.43
  • Java 1.8.0_144
  • Apache Maven 3.3.9

似乎是类加载器问题 - 我在某个地方读到过,GT JAR文件在一些Tomcat版本中也存在问题..值得检查。 - Sheetal Mohan Sharma
1
因为您可以在JAR文件中拥有一个类的多个版本,所以类路径顺序存在,以解决这些问题。 - LMC
@LuisMuñoz,我不确定我是否理解了您的意思,您能详细说明一下吗?如果我理解正确,Tomcat不允许直接指定类路径顺序,它只有搜索位置的顺序:JVM、WEB-INF/classes、WEB-INF/lib等(https://tomcat.apache.org/tomcat-8.0-doc/class-loader-howto.html)。据我所知,我的`WEB-INF/lib`目录中只有一个jar包包含`DefaultEllipsoid`类。然而,我的问题似乎与底层文件系统中`WEB-INF/lib`中文件的顺序有关。 - Chris Finley
1
首先,类可以在启动时加载,但在创建类的实例时也可以在运行时加载另一个版本。尝试在WEB-INF/lib文件夹上运行此命令 for j in *.jar; do echo "----> $j"; jar -tvf $j | grep 'your\.class\.name' ; done 以验证该类不在2个或更多的JAR文件中存在。 - LMC
我运行了您的命令并验证该类只出现在一个JAR文件中。 - Chris Finley
我以前见过这种情况,我和一个同事一起工作,他不知道如何构建Maven项目。他一直在更改版本,并且经常忘记清理项目。这正是为什么您应该始终使用相同的“中立”环境来构建的原因,从共享虚拟盒子上直接检出所有内容,然后进行构建和部署。 - Palcente
4个回答

1

这行代码:

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

DefaultEllipsoid类被找到了,但是要使其有效,需要加载一些其他的类,但加载失败了,另一个类无效。

可能是由于这个类有两个非常不同的版本,或者代码编译时使用了一种版本,在运行时使用了另一种带有不同方法签名的版本。

此外,从tomcat8开始,WEB-INF/lib中的应用程序jar文件不再按字母顺序加载。我想在tomcat网站上有相关文档,但现在我找不到了,只找到了一个tomcat bugzilla bug 57129的回归错误(不会修复)。

这个类加载器的问题意味着,如果您更改了WEB-INF/lib中的某些内容并重新启动Tomcat,则会随机加载一些类,如果存在重复的jar版本,则会以一种或另一种方式加载您的应用程序。

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

这个答案指出了我所缺失的关键信息:DefaultEllipsoid 不是重复的类,而是它的一个 _导入_。实际上,它是其中一个导入的超类:DefaultEllipsoid 导入 EllipsoidEllipsoid 扩展了 IdentifiedObject,而 IdentifiedObject 包含在多个 jar 中。如果知道如何正确使用,我敢打赌有一个 maven 插件(也许是 maven 依赖插件)可以帮助检测这种问题。 - Chris Finley

0

根据K. Sopheak和amod(版主)的要求,我进行了更新。

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

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


@K.Sopheak 我的帖子是对问题的回答。我引用了问题本身的一部分,正如你所看到的,给出了一个答案。顺便说一句,我不明白为什么我的回答会被踩。 - Eugène Adell
这并没有真正回答问题。如果你有不同的问题,可以点击提问来询问。你也可以添加赏金来吸引更多人关注这个问题。- 来自审核 - amod
@amod 在评论之前,你应该先阅读问题和答案,因为很明显我的回答是针对这个问题的答案。 - Eugène Adell
我在使用完全空的workDir目录启动Tomcat时仍然遇到相同的异常。 - Chris Finley
@ChrisFinley,你在**$TOMCAT_BASE/lib**目录下有哪些JAR包? - Eugène Adell
仅包括干净的Tomcat安装中的以下内容: annotations-api.jar catalina-ant.jar catalina-ha.jar catalina.jar catalina-storeconfig.jar catalina-tribes.jar ecj-4.5.1.jar el-api.jar jasper-el.jar jasper.jar jaspic-api.jar jsp-api.jar servlet-api.jar tomcat-api.jar tomcat-coyote.jar tomcat-dbcp.jar tomcat-i18n-es.jar tomcat-i18n-fr.jar tomcat-i18n-ja.jar tomcat-jdbc.jar tomcat-jni.jar tomcat-util-scan.jar tomcat-util.jar tomcat-websocket.jar websocket-api.jar - Chris Finley

0
罐子的顺序真的很重要。 基本上,类加载器会以这种方式加载它们。 因此,当有多个版本的同一类时,您可能会遇到NoClassdefFoundError或java.lang.IncompatibleClassChangeError: 它能够看到您的类,但不确定要使用哪一个或类已被修改并放置在多个罐子中。检查您的罐子是否有任何重复的类,然后您可以消除根本原因。

正如我在问题中提到并在评论中验证的那样,它所抱怨的类不是包含在多个jar文件中。我尚未验证是否存在_没有_重复类(事实上,我相信至少有几个隐藏起来...),但NoClasDefFoundError类仅在一个jar文件中。 - Chris Finley
@vivekananda 不是每次都这样,Tomcat改变了它的加载顺序。请看我的回答和评论。 - Eugène Adell

0
在类似的问题什么导致IncompatibleClassChangeError上提供了几个有趣的答案,这表明您和其他开发人员在编译时没有使用完全相同的jar文件。请比较每个用于编译的jar文件(至少比较文件大小,但最好比较校验和),以及运行时使用的jar文件,当然还包括您自己使用的和其他开发人员使用的jar文件。

在发布问题之前,我已经验证了WEB-INF/lib目录下的内容是否相同(通过比较jar文件的md5sums),除了文件的目录顺序之外,两个war版本之间是一致的。 - Chris Finley
这是一个非常庞大/复杂的Maven项目,具有许多依赖项,因此定位和比较用于编译的所有jar文件可能是一项挑战,但我认为您可能会在编译时/运行时jar之间发现差异。也许是某种传递依赖版本差异或其他原因。 - Chris Finley
许多团队使用共同的代码库来确保可预测的结果,特别是在项目庞大或复杂时。尽管您明确表示您没有两个相同的类,但有些回答说您有两个相同的类。我看不到其他解决方案,只能检查每个人在编译时是否使用相同的类,然后在打包时进行检查。此外,还需要考虑回答悬赏问题所需的时间。 - Eugène Adell
尽管我们几乎总是使用mvn clean install进行构建,它将在启动之前删除包含编译类的目标目录。可能本地的~/.m2/repository中存在一些陈旧的东西,但我相信我们都尝试清除了它们。有趣的是,当源代码在挂载的网络文件系统上构建时才会生成错误的war文件,将代码目录移动到本地磁盘并从那里构建会生成一个有效的构建成果。 - Chris Finley
我在考虑这样的事情,因为你的ls -U命令返回不同的结果,显示可能有不同的文件系统类型。 - Eugène Adell
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接