如何处理 Java 中的 LinkageErrors?
- 2024-10-25 08:42:00
- admin 原创
- 75
问题描述:
在开发基于 XML 的 Java 应用程序时,我最近在 Ubuntu Linux 上遇到了一个有趣的问题。
我的应用程序使用Java 插件框架,似乎无法将dom4j创建的 XML 文档转换为Batik 的SVG 规范实现。
在控制台上,我了解到发生了一个错误:
线程“AWT-EventQueue-0”中的异常 java.lang.LinkageError:接口 itable 初始化中加载器约束违规:解析方法“org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg/w3c/dom/Attr;”时,当前类 org/apache/batik/dom/svg/SVGOMDocument 的类加载器(org/java/plugin/standard/StandardPluginClassLoader 的实例)和接口 org/w3c/dom/Document 的类加载器(<bootloader> 的实例)对于签名中使用的类型 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 系统上。
你们中是否有人遇到过这样的问题并且知道如何解决它或者至少找到问题的核心?
解决方案 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 - 作为根的子项的子项
等等
通常,类加载器会将加载委托给其父类加载器(这称为“ 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/应用服务器环境中部署应用程序,但如果您的环境设置了类加载器层次结构,那么我的冗长解释可能对您适用。是吗?JPF 是否在执行某种类加载器魔法以实现其插件功能?
解决方案 3:
这可能对某些人有帮助,因为对我来说效果很好。可以通过集成您自己的依赖项来解决此问题。请按照以下简单步骤操作
首先检查错误,应该是这样的:
方法执行失败:
java.lang.LinkageError:加载器约束违规:
当解析方法“org.slf4j.impl.StaticLoggerBinder .getLoggerFactory()Lorg/slf4j/ILoggerFactory;”时
当前类的类加载器(org/openmrs/module/ModuleClassLoader 的实例), org/slf4j/ LoggerFactory,
以及已解析类的类加载器(org/apache/catalina/loader/WebappClassLoader 的实例), org/slf4j/impl/ StaticLoggerBinder,
对于签名中使用的类型 taticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory; 有不同的 Class 对象
查看两个突出显示的类。在 Google 上搜索它们,例如“StaticLoggerBinder.class jar 下载”和“LoggeraFactory.class jar 下载”。这将向您显示第一个或在某些情况下是第二个链接(站点是http://www.java2s.com),这是您在项目中包含的 jar 版本之一。您可以自己巧妙地识别它,但我们沉迷于谷歌 ;)
之后你就会知道 jar 文件名,在我的情况下它就像 slf4j-log4j12-1.5.6.jar & slf4j-api-1.5.8
现在该文件的最新版本可以在这里获得http://mvnrepository.com/(实际上是迄今为止的所有版本,这是 maven 从中获取依赖项的网站)。
现在将两个文件添加为最新版本的依赖项(或保持两个文件版本相同,所选版本均为旧版本)。以下是您必须在 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>
解决方案 4:
你能指定类加载器吗?如果不能,请尝试像这样指定上下文类加载器:
Thread thread = Thread.currentThread();
ClassLoader contextClassLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(yourClassLoader);
callDom4j();
} finally {
thread.setContextClassLoader(contextClassLoader);
}
我不熟悉 Java 插件框架,但我为 Eclipse 编写代码,并且时不时会遇到类似的问题。我不能保证它会修复它,但可能值得一试。
解决方案 5:
Alex 和 Matt 的回答非常有帮助。他们的分析也让我受益匪浅。
我在 Netbeans RCP 框架中使用 Batik 库时遇到了同样的问题,Batik 库作为“库包装器模块”包含在内。如果其他模块使用 XML API,并且该模块不需要且不建立对 Batik 的依赖,则会出现类加载器约束违规问题,并出现类似的错误消息。
在 Netbeans 中,各个模块使用专用的类加载器,模块之间的依赖关系意味着合适的类加载器委托路由。
我可以通过简单地从 Batik 库包中省略 xml-apis jar 文件来解决这个问题。
解决方案 6:
正如在这个问题中指定的,启用-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]
解决方案 7:
我发现这个类被加载了两次。发现原因是 parallelWebappClassLoader 先自己加载类,而不是使用它的父类加载器。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件