在Java / Maven中,如何处理"Xerces hell" | Java Debug 笔记

715 阅读5分钟

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看活动链接


问题描述

在我的办公室里,仅仅是提到"Xerces"这个词就能引起开发者们的震怒了。我粗略浏览了一下在Stack Overflow上的其他关于Xerces的问题,发现几乎所有Maven用户在某个时候都被该问题“感动”了。不幸的是,要理解这个问题,就需要对Xerces的历史有所了解......

历史

  • Xerces是Java生态系统中使用最广泛的XML解析器。几乎所有用Java编写的库或框架都以某种能力使用Xerces(如果不是直接使用,就是间接使用)。
  • 到目前为止,官方二进制文件中包含的Xerces jar尚未进行版本控制。例如,Xerces 2.11.0实现的jar名为xercesImpl.jar,而不是xercesImpl-2.11.0.jar
  • Xerces团队不使用Maven,这意味着他们不会将正式发行版上载到Maven Central
  • xerces曾经以一个单独的jar发布(xerces.jar),但分为两个jar包——一个包含API(xml-apis.jar),一个包含这些API的实现(xercesImpl.jar)。许多较旧的Maven POM仍然声明对xerces.jar的依赖。在过去的某个时候,Xerces也以xmlParserAPIs.jar的形式发布,还有某些较早的POM也会依赖。
  • 被那些部署Maven存储库的jar分配给xml的api和xercesImpl jar版本通常是不同的。例如,xml的api可能考虑到版本1.3.03和xercesImpl可能给定版本2.8.0,即使是从xerces-c++2.8.0。这是因为人们常常标记xml的api和规范,它实现的版本不一致。有一个非常好的但不完整的说明在这里
  • 更复杂的是,Xerces是包括在JRE中用于XML处理的Java API的参考实现中的XML解析器(JAXP)。实现类在com.sun.*名字空间下被重新包装,这使得直接访问他们十分危险,因为他们可能没法在一些JRE中使用。然而,并不是所有Xerces的功能都是通过java.*javax.*的API进行公开的;例如,没有API公开Xerces序列化。
  • 更让人困惑的是,几乎所有的servlet容器(JBoss、Jetty、Glassfish、Tomcat等)在它们的/lib文件夹中都附带了Xerces。

问题

解决冲突

由于上述某些原因(或者可能是全部原因),许多组织在其POM中发布和使用Xerces的定制构建。如果你有一个小的应用程序,并且只使用Maven Central,这不是一个真正的问题,但它迅速成为企业软件的一个问题,Artifactory或联系代理多个存储库(JBoss、Hibernate等):

image.png 例如,组织A可能会将xml-apis发布为:

<groupId>org.apache.xerces</groupId>
<artifactId>xml-apis</artifactId>
<version>2.9.1</version>

与此同时,组织B可能会将相同的jar发布为:

<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.3.04</version>

虽然B的jar是一个较低的版本,但由于它们有不同的groupId,所以Maven不知道它们是相同的构件。因此,它不能执行冲突解决,并且jar都将被包含为已解决的依赖项:

image.png

类加载器难题

如上所述,JRE在JAXP RI中是随着Xerces一起发布的。虽然最好将所有Xerces Maven依赖项标记为<exclusion><provided>,但您所依赖的第三方代码可能与您使用的JDK的JAXP中提供的版本兼容,同时也可能不兼容。此外,还有servlet容器中附带的Xerces jar需要处理。这给您留下了许多选择:是否删除servlet版本并希望容器在JAXP版本上运行?最好不要使用servlet版本,而希望您的应用程序框架运行在servlet版本上?如果上面列出的一个或两个未解决的冲突进入您的产品(这在大型组织中是很容易发生的),您很快就会发现自己处于类加载器地狱,想知道类加载器在运行时选择的Xerces版本以及是否将在Windows和Linux中选择相同的jar(可能不是)。

解决方案?

我们已经尝试将所有Xerces Maven依赖项标记为<provided><exclusion>,但这很难实施(特别是在大型团队中),因为构件有这么多别名(xml-apixercesxercesImplxmlparserAPIs等)。此外,我们的第三方库/框架可能不能在JAXP版本或servlet容器提供的版本上运行。

我们如何用Maven最好地解决这个问题?我们是否必须对依赖项进行这样的细粒度控制,然后依赖分层类加载?是否有某种方法可以全局排除所有Xerces依赖项,并强制所有框架/库使用JAXP版本?


更新:Joshua Spiewak已经上传了Xerces构建脚本的补丁版本到允许上传到Maven Central的XERCESJ-1454


部分高赞回答

最高赞回答(115赞)

自2013年2月20日以来,Maven Central中有2.11.0 JAR (和JAR的源码)详见Maven Central中的Xerces。我想知道为什么他们还没有解决:issues.apache.org/jira/browse… 我用过:

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

而且所有的依赖都被很好地解决了——甚至是合适的xml-apis-1.4.01!最重要的是(在过去并不明显)- Maven Central中的JAR与官方Xerces-J-bin.2.11.0.zip发行版中的JAR是相同的。然而,我无法找到xml-schema-1.1-beta版本——由于附加的依赖关系,它不能是Maven分类版。

第二高赞回答(64赞)

坦率地说,我们遇到的几乎所有东西在JAXP版本中都能正常工作,所以我们总是排除xml-apis和xercesImpl。

第三高赞回答(43赞)

你可以使用maven enforcer插件和被禁止的依赖规则。 这将允许您禁止所有您不想要的别名,只允许您想要的别名。 当违反这些规则时,项目的maven构建将失败。 此外,如果这个规则适用于一个企业中的所有项目,你可以把插件配置放到一个公司的父pom中。 详见:

第四高赞回答(36赞)

我知道这并不能准确地回答这个问题,但是对于那些从谷歌来的、碰巧使用Gradle进行依赖管理的人: 我设法摆脱所有的xerces/Java8问题与Gradle像这样:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}

本文翻译自Stack Overflow,翻译部分参考来源自这里。关于翻译文章,聆风也相对生疏,难免有错漏,欢迎各位大佬在评论区批评指正,谢谢!