在部署后出现的NoClassDefFoundError: org/w3c/dom/ls/DocumentLS问题 - 编译时已经修复

12

背景

我有一个项目,需要解析一些XML文档,我需要使用xerces依赖库:

<dependency>
    <groupId>xerces</groupId>
    <artifactId>xerces</artifactId>
    <version>2.4.0</version>
</dependency>

使用 junit4 编写单元测试时,每次运行单元测试时都会遇到以下问题,在使用 mvn clean install 进行编译时每次都会出现:

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.346 s <<< FAILURE! - in ConversionTest
[ERROR] ConversionTest.initializationError  Time elapsed: 0.054 s  <<< ERROR!
java.lang.NoClassDefFoundError: org/w3c/dom/ls/DocumentLS
        at ConversionTest.fromDirectory(ConversionTest.java:92)
        at ConversionTest.data(ConversionTest.java:65)
Caused by: java.lang.ClassNotFoundException: org.w3c.dom.ls.DocumentLS
        at ConversionTest.fromDirectory(ConversionTest.java:92)
        at ConversionTest.data(ConversionTest.java:65)

编译时解决方案

在浏览网页时,我意识到我需要将一个新的依赖项添加到我的pom.xml文件中:

<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.11.0</version>
</dependency>

在这样做后,测试编译通过了,我可以生成我的.jar文件,该文件是使用以下构建插件打包的:

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>lib/</classpathPrefix>
                        <mainClass>com.company.tools.Application</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin> 

...并使用以下设置编译:

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven.compiler.plugin.version}</version>
            <configuration>
                <encoding>cp1252</encoding>
                <release>11</release>
                <fork>true</fork>
                <meminitial>128m</meminitial>
                <maxmem>512m</maxmem>
                <compilerArgs>
                    <arg>-Xpkginfo:always</arg>
                </compilerArgs>
            </configuration>
        </plugin>

这将生成一个.jar文件,其中包含所有所需的依赖项,包括著名的org/w3c/dom/ls/DocumentLS:

进入图像描述

部署

现在我将这个.jar文件移动到我的服务器并尝试使用以下命令运行它:

java -jar myJar.jar <inputs>

每当我这样做时,我总是再次遇到以下异常!

Exception in thread "main" java.lang.NoClassDefFoundError: org/w3c/dom/ls/DocumentLS
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
        at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:801)
        at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:699)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:622)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at org.apache.xerces.jaxp.DocumentBuilderImpl.<init>(Unknown Source)
        at org.apache.xerces.jaxp.DocumentBuilderFactoryImpl.newDocumentBuilder(Unknown Source)
        at com.company.tools.impl.FileProviderImpl.getXmlFile(FileProviderImpl.java:68)
        at com.company.tools.impl.FileProviderImpl.<init>(FileProviderImpl.java:38)
        at com.company.tools.impl.FileProviderImpl$Builder.build(FileProviderImpl.java:91)
        at com.company.tools.Application.main(Application.java:50)
Caused by: java.lang.ClassNotFoundException: org.w3c.dom.ls.DocumentLS
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 15 more

我的问题和机器的一些细节

我在这里有点迷失。我已经在我的pom.xml中添加了依赖项,类也被正确地打包在了.jar中,但仍然存在相同的问题。 我做错了什么?

如果可以帮助的话:

我的机器

Java version: 11.0.2-BellSoft, vendor: BellSoft, runtime: C:\jdk-11.0.2
Default locale: fr_FR, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

我的服务器:

openjdk version "11" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
OS: Linux myServerAddress 3.10.0-327.el7.x86_64 #1 SMP Thu Oct 29 17:29:29 EDT 2015 x86_64 x86_64 x86_64 GNU/Linux

提前致谢!


你能试试这个版本 2.12.0 吗? - Sambit
@Sambit 同样的问题,不幸的是。 - Matteo NNZ
请查看此链接:https://github.com/capstone-coal/coal-sds/issues/32。 - Sambit
@Sambit,如果我检查修复过的内容,我会明白他的某些依赖项引用了与所需版本发生冲突的xercesImpl的不同版本。 但这并不是我的情况,除了test范围的xerces和junit之外,我在Pom中基本上没有任何依赖项... - Matteo NNZ
1
@Sambit,你最终是正确的,答案就在你建议的Github问题中,只是我没有想到接口会对实现产生传递依赖(加上Maven中的依赖树也没有看到它)。非常感谢! - Matteo NNZ
1个回答

15
我最终找到了解决方案,多亏了@Sambit的第二条评论,有关此 GitHub 问题。在这里发布答案,希望它可以节省其他人几天的头痛!
基本上,我在我的pom.xml中有这个:
<dependencies>
    <dependency>
        <groupId>xerces</groupId>
        <artifactId>xerces</artifactId>
        <version>2.4.0</version>
    </dependency>
    <dependency>
        <groupId>xerces</groupId>
        <artifactId>xercesImpl</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

在两种情况下(单元测试和主代码),异常都是由此代码引发的:

try(InputStream is = new FileInputStream(file)) {
    documents.put(file.getName(), DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is));
}

具体来说,是通过对newDocumentBuilder()的调用来寻找 DocumentImpl类型的实现。

问题说明

第一个依赖项xerces正在拉取一个被弃用版本xercesImpl的传递依赖项。因此,当我运行我的测试时,代码能够编译(因为依赖项存在),但是当 newDocumentBuilder() 寻找DocumentImpl时,实现被错误的依赖项返回,它正在寻找org/w3c/dom/ls/DocumentLS 未成功,从而引发了NoClassDefFoundError

一旦在我的pom中添加了对xercesImpl的显式依赖关系,junit运行程序便明白了应该在显式依赖项中查找DocumentImpl,而不是在已被弃用的xercesImpl版本中查找。所以问题得到解决。

然而,在服务器上运行程序的JVM并没有做出同样的假设: newDocumentBuilder()仍然在传递依赖项中寻找DocumentImpl,所以问题仍然存在。

解决方法

摆脱传递依赖项:

<dependencies>
    <dependency>
        <groupId>xerces</groupId>
        <artifactId>xerces</artifactId>
        <version>2.4.0</version>
        <exclusions>
            <exclusion>
                <groupId>xerces</groupId>
                <artifactId>xercesImpl</artifactId>
            <exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>xerces</groupId>
        <artifactId>xercesImpl</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>    

您也可以反转这两个依赖项的顺序: <dependency> <groupId>xerces</groupId> <artifactId>xerces</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>xerces</groupId> <artifactId>xercesImpl</artifactId> <version>2.11.0</version> </dependency>如果在调用xerces之前调用它,Maven会采用第一个调用,因此您不需要在xerces中排除xercesImpl。 - pacataque
太好了,我也解决了类似的 Spring Boot 项目问题。 - Garry Dias

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