63

在开发一个大量基于 XML 的 Java 应用程序时,我最近在 Ubuntu Linux 上遇到了一个有趣的问题。

我的应用程序使用Java Plugin Framework似乎无法将dom4j创建的XML 文档转换为Batik 的SVG 规范实现。

在控制台上,我了解到发生了错误:

线程“AWT-EventQueue-0”java.lang.LinkageError 中的异常:接口 itable 初始化中的加载程序约束冲突:解析方法“org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg 时/w3c/dom/属性;" 当前类 org/apache/batik/dom/svg/SVGOMDocument 的类加载器(org/java/plugin/standard/StandardPluginClassLoader 的实例)和接口 org/w3c/ 的类加载器(<bootloader> 的实例) dom/Document 对于签名中使用的 org/w3c/dom/Attr 类型有不同的 Class 对象
    在 org.apache.batik.dom.svg.SVGDOMImplementation.createDocument(SVGDOMImplementation.java:149)
    在 org.dom4j.io.DOMWriter.createDomDocument(DOMWriter.java:361)
    在 org.dom4j.io.DOMWriter.write(DOMWriter.java:138)

我认为问题是由来自 JVM 的原始类加载器和插件框架部署的类加载器之间的冲突引起的。

据我所知,不可能为要使用的框架指定类加载器。破解它可能是可能的,但我更喜欢用不那么激进的方法来解决这个问题,因为(无论出于何种原因)它只发生在 Linux 系统上。

你们当中有人遇到过这样的问题吗?知道如何解决它或至少找到问题的核心吗?

4

7 回答 7

73

LinkageError 是您在一个经典案例中会遇到的情况,其中您有一个由多个类加载器加载的类 C,并且这些类在同一代码中一起使用(比较、强制转换等)。如果它是相同的类名,或者即使它是从相同的 jar 加载的,都没有关系 - 如果从另一个类加载器加载,来自一个类加载器的类总是被视为不同的类。

这条信息(这些年来已经有了很大的改进)说:

Exception in thread "AWT-EventQueue-0" java.lang.LinkageError: 
loader constraint violation in interface itable initialization: 
when resolving method "org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg/w3c/dom/Attr;" 
the class loader (instance of org/java/plugin/standard/StandardPluginClassLoader) 
of the current class, org/apache/batik/dom/svg/SVGOMDocument, 
and the class loader (instance of ) for interface org/w3c/dom/Document 
have different Class objects for the type org/w3c/dom/Attr used in the signature

因此,这里的问题在于解决 SVGOMDocument.createAttribute() 方法,该方法使用 org.w3c.dom.Attr(标准 DOM 库的一部分)。但是,使用 Batik 加载的 Attr 版本是从与您传递给方法的 Attr 实例不同的类加载器加载的。

您会看到 Batik 的版本似乎是从 Java 插件加载的。而你的是从“”加载的,这很可能是内置的 JVM 加载器之一(引导类路径、ESOM 或类路径)。

三个突出的类加载器模型是:

  • 委托(JDK 中的默认设置 - 先问父母,然后再问我)
  • 委托后(常见于插件、servlet 和您想要隔离的地方 - 询问我,然后询问父母)
  • 兄弟(在 OSGi、Eclipse 等依赖模型中很常见)

我不知道 JPF 类加载器使用什么委托策略,但关键是您希望加载一个版本的 dom 库,并且每个人都可以从同一位置获取该类。这可能意味着将其从类路径中删除并作为插件加载,或者阻止 Batik 加载它,或者其他什么。

于 2008-10-28T22:00:50.907 回答
22

听起来像是类加载器层次结构问题。我不知道您的应用程序部署在哪种类型的环境中,但有时这个问题可能发生在 Web 环境中 - 应用程序服务器创建类加载器的层次结构,类似于:

javahome/lib - 作为 root
appserver/lib - 作为 root
webapp/WEB-INF/lib 的子级 - 作为 root
等的子级的子级

通常类加载器将加载委托给它们的父类加载器(这被称为“ parent-first”),如果该类加载器找不到该类,则子类加载器会尝试。例如,如果一个在 webapp/WEB-INF/lib 中部署为 JAR 的类尝试加载一个类,它首先请求 appserver/lib 对应的类加载器加载该类(进而请求 javahome/lib 对应的类加载器加载类),如果此查找失败,则搜索 WEB-INF/lib 以查找与此类的匹配项。

在 Web 环境中,您可能会遇到此层次结构的问题。例如,我之前遇到的一个错误/问题是 WEB-INF/lib 中的类依赖于部署在 appserver/lib 中的类,而后者又依赖于部署在 WEB-INF/lib 中的类。这导致了失败,因为虽然类加载器能够委托给父类加载器,但它们不能委托回树下。因此,WEB-INF/lib 类加载器会向 appserver/lib 类加载器请求一个类,appserver/lib 类加载器会加载该类并尝试加载依赖类,但由于在 appserver/lib 或 javahome 中找不到该类而失败/库。

因此,虽然您可能没有在 web/app 服务器环境中部署您的应用程序,但如果您的环境设置了类加载器的层次结构,那么我过长的解释可能适用于您。可以?JPF 是否在使用某种类加载器魔法来实现它的插件功能?

于 2008-10-28T20:22:46.033 回答
6

可能这会对某人有所帮助,因为它对我来说非常好。该问题可以通过集成您自己的依赖项来解决。按照这个简单的步骤

首先检查应该是这样的错误:

  • 方法执行失败:
  • java.lang.LinkageError:加载程序约束违规:
  • 解析方法“org.slf4j.impl.StaticLoggerBinder.getLoggerFactory ( )Lorg/slf4j/ILoggerFactory;”时
  • 当前类 org/slf4j/ LoggerFactory的类加载器(org/openmrs/module/ModuleClassLoader 的实例) ,
  • 以及用于已解析类 org/slf4j/impl/ StaticLoggerBinder的类加载器(org/apache/catalina/loader/WebappClassLoader 的实例) ,
  • taticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory 类型有不同的类对象;在签名中使用

  1. 请参阅两个突出显示的类。谷歌搜索它们,如“StaticLoggerBinder.class jar 下载”和“LoggeraFactory.class jar 下载”。这将向您显示第一个或在某些情况下是第二个链接(站点为http://www.java2s.com),这是您在项目中包含的 jar 版本之一。您可以自己巧妙地识别它,但我们沉迷于谷歌;)

  2. 之后你会知道 jar 文件名,在我的例子中它就像 slf4j-log4j12-1.5.6.jar & slf4j-api-1.5.8

  3. 现在该文件的最新版本可在此处获得http://mvnrepository.com/(实际上是迄今为止的所有版本,这是 maven 获取依赖项的站点)。
  4. 现在将两个文件添加为具有最新版本的依赖项(或保持两个文件版本相同,任何一个选择的版本都是旧的)。以下是您必须包含在 pom.xml 中的依赖项

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.7</version>
</dependency>

如何从 Maven 站点获取依赖定义

于 2014-06-24T20:03:35.527 回答
5

你能指定一个类加载器吗?如果没有,请尝试像这样指定上下文类加载器:

Thread thread = Thread.currentThread();
ClassLoader contextClassLoader = thread.getContextClassLoader();
try {
    thread.setContextClassLoader(yourClassLoader);
    callDom4j();
} finally {
    thread.setContextClassLoader(contextClassLoader);
}

我不熟悉 Java 插件框架,但我为 Eclipse 编写代码,并且我不时遇到类似的问题。我不保证它会修复它,但它可能值得一试。

于 2008-10-28T20:28:20.360 回答
3

Alex 和 Matt 的回答非常有帮助。我也可以从他们的分析中受益。

在 Netbeans RCP 框架中使用 Batik 库时,我遇到了同样的问题,Batik 库作为“库包装模块”包含在内。如果其他一些模块使用 XML api,并且不需要为该模块建立对 Batik 的依赖,则会出现类加载器约束违规问题并出现类似的错误消息。

在 Netbeans 中,各个模块使用专用的类加载器,模块之间的依赖关系意味着合适的类加载器委托路由。

我可以通过简单地从 Batik 库包中省略 xml-apis jar 文件来解决问题。

于 2009-11-23T16:56:06.780 回答
1

正如在这个问题中所指定的那样,启用-verbose:classJVM 会记录有关正在加载的所有类的信息,这对于了解在更复杂的场景和应用程序中类的来源非常有帮助。

您得到的输出大致如下(从那个问题复制):

[Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar]
[Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
于 2019-02-18T09:19:15.663 回答
1

我发现这个类被加载了两次。找到原因是parallelWebappClassLoader首先自己加载类而不是使用它的父类加载器。

于 2019-10-29T12:00:32.897 回答