开发一个基于XML的大量Java应用程序,我最近在Ubuntu Linux上遇到了一个有趣的问题。
我的应用程序使用Java Plugin Framework似乎无法将dom4j创建的XML文档转换为Batik's 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系统上。
你们其中一个人遇到过这样的问题,并且知道如何解决这个问题或者至少解决问题的核心问题?
答案 0 :(得分:60)
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或类路径)。
三个着名的类加载器模型是:
我不知道JPF类加载器使用什么委派策略,但关键是你想要加载一个版本的dom库,并且每个人都要从同一个位置获取该类。这可能意味着将其从类路径中删除并作为插件加载,或阻止Batik加载它或其他东西。
答案 1 :(得分:17)
听起来像是一个类加载器层次结构问题。我不知道你的应用程序部署在什么类型的环境中,但有时这个问题可能发生在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中找到该类/ lib中。
因此,虽然您可能没有在Web / app服务器环境中部署应用程序,但如果您的环境具有设置的类加载器层次结构,那么我太长的解释可能适用于您。可以? JPF是否能够实现某种类加载器魔法以实现它的插件功能?
答案 2 :(得分:6)
这可能对某人有所帮助,因为它对我来说非常好。可以通过集成自己的依赖项来解决该问题。按照这个简单的步骤
查看两个突出显示的课程。谷歌搜索他们像&#34; StaticLoggerBinder.class jar下载&#34; &安培; &#34; LoggeraFactory.class jar下载&#34;。这将首先显示或在某些情况下显示第二个链接(站点为http://www.java2s.com),这是您在项目中包含的jar版本之一。你可以自己聪明地识别它,但我们沉迷于谷歌;)
之后你会知道jar文件名,在我的例子中它就像slf4j-log4j12-1.5.6.jar&amp; SLF4J-API-1.5.8
<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>
答案 3 :(得分:5)
你能指定一个类加载器吗?如果没有,请尝试指定上下文类加载器,如下所示:
Thread thread = Thread.currentThread();
ClassLoader contextClassLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(yourClassLoader);
callDom4j();
} finally {
thread.setContextClassLoader(contextClassLoader);
}
我不熟悉Java插件框架,但是我为Eclipse编写代码,并且我不时遇到类似的问题。我不保证它会修复它,但它可能值得一试。
答案 4 :(得分:3)
亚历克斯和马特的答案非常有帮助。我也可以从他们的分析中受益。
在Netbeans RCP框架中使用Batik库时遇到了同样的问题,Batik库被包含为“Library Wrapper Module”。如果某个其他模块使用XML apis,并且不需要为该模块建立对Batik的依赖,则类似的错误消息会出现类加载器约束违规问题。
在Netbeans中,各个模块使用专用的类加载器,模块之间的依赖关系意味着合适的类加载器委派路由。
我可以通过简单地从Batik库包中省略xml-apis jar文件来解决问题。
答案 5 :(得分:0)
如this question中所述,启用-verbose:class
将使JVM日志记录有关所有正在加载的类的信息,这对于了解类在更复杂的场景和应用程序中的来源很有帮助。
您得到的输出大致类似于此(从该问题复制):
[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]
答案 6 :(得分:0)
我发现该类被加载了两次。发现原因是parallelWebappClassLoader首先自行加载类,而不是使用其父类Loader。