如何处理Java中的LinkedErrors?

2022-08-31 15:11:00

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

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

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

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 <bootloader>) for interface org/w3c/dom/Document have different Class objects for the type org/w3c/dom/Attr used in the signature
    at org.apache.batik.dom.svg.SVGDOMImplementation.createDocument(SVGDOMImplementation.java:149)
    at org.dom4j.io.DOMWriter.createDomDocument(DOMWriter.java:361)
    at org.dom4j.io.DOMWriter.write(DOMWriter.java:138)

我认为这个问题是由JVM的原始类装入器和插件框架部署的类装入器之间的冲突引起的。

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

你们中的某个人是否遇到过这样的问题,并且知道如何解决它或至少找到问题的核心?


答案 1

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 加载它,或者其他内容。


答案 2

听起来像是类装入器层次结构问题。我无法判断您的应用程序部署在哪种类型的环境中,但有时在Web环境中可能会出现此问题 - 其中应用程序服务器创建类装入器的层次结构,类似于以下内容:

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

通常,类装入器将装入委托给其父类装入器(这称为“”),如果该类装入器找不到该类,则子类装入器会尝试这样做。例如,如果在 webapp/WEB-INF/lib 中部署为 JAR 的类尝试加载一个类,它首先要求对应于 appserver/lib 的类加载器加载该类(这反过来又要求对应于 javahome/lib 的类加载器加载该类),如果此查找失败,则搜索 WEB-INF/lib 以查找与该类匹配的类。parent-first

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

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


推荐