在我的办公室里,仅仅提到Xerces一词就足以激起开发商的愤怒。粗略地看一下Xerces关于SO的其他问题,似乎表明几乎所有Maven用户都在某个时刻被这个问题“触动”了。不幸的是,了解这个问题需要一些关于薛西斯历史的知识。。。

历史

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(xmlapis.jar),另一个包含这些API的实现(xercesImpl.jar)。许多旧的Maven POM仍然声明对Xerces.jar的依赖。在过去的某个时候,Xerces也被发布为xmlParserAPIs.jar,一些旧的POM也依赖于此。将jar部署到Maven存储库的人员分配给xmlapi和xercesImpl jar的版本通常不同。例如,xml api的版本可能为1.3.03,xercesImpl的版本可能是2.8.0,尽管两者都来自Xerces 2.8.0。这是因为人们经常用它实现的规范版本来标记xmlapis jar。这里有一个非常好的,但不完整的分解。更复杂的是,Xerces是JRE中包含的Java API for XML Processing(JAXP)参考实现中使用的XML解析器。实现类在com.sun.*命名空间下重新打包,这使得直接访问它们很危险,因为它们在某些JRE中可能不可用。然而,并非所有Xerces功能都通过java.*和javax.*API公开;例如,没有公开Xerces序列化的API。更令人困惑的是,几乎所有的servlet容器(JBoss、Jetty、Glassfish、Tomcat等)都在一个或多个/lib文件夹中附带Xerces。

问题

冲突解决方案

出于上述原因组织在其聚甲醛。如果您有一个小型应用程序,并且只使用Maven Central,这并不是一个真正的问题,但对于Artifactry或Nexus代理多个存储库(JBoss、Hibernate等)的企业软件来说,这很快就会成为一个问题:

例如,组织A可以将xmlapi发布为:

<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比a的jar版本低,但Maven不知道它们是同一个工件,因为它们有不同的组ID。因此,它无法执行冲突解决和两者jar将作为已解析的依赖项包含:

Classloader地狱

如上所述,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>,但这很难实施(尤其是在大型团队中),因为工件有太多别名(xmlapi、Xerces、xercesImpl、xmlParserAPI等)。此外,我们的第三方libs/framework可能无法在JAXP版本或servlet容器提供的版本上运行。

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


更新:Joshua Spiewak已将Xerces构建脚本的补丁版本上传到XERCESJ-1454,允许上传到Maven Central。投票/观看/贡献这个问题,让我们一劳永逸地解决这个问题。


当前回答

我想你需要回答一个问题:

是否存在一个xerces*.jar,您的应用程序中的所有内容都可以使用它?

如果不是的话,你基本上就完蛋了,必须使用OSGI这样的工具,它允许你同时加载不同版本的库。请注意,它基本上将jar版本问题替换为类加载器问题。。。

如果存在这样的版本,您可以让您的存储库为所有类型的依赖项返回该版本。这是一个丑陋的黑客,最终会在类路径中多次使用相同的xerces实现,但比使用多个不同版本的xerce要好。

您可以排除对xerces的每个依赖项,并将其添加到您想要使用的版本中。

我想知道你是否可以写一些版本解析策略作为maven的插件。这可能是最好的解决方案,但如果可行的话,需要一些研究和编码。

对于运行时环境中包含的版本,您必须确保在考虑服务器的lib文件夹之前,将其从应用程序类路径中删除,或者首先考虑应用程序jar进行类加载。

所以总结一下:这是一片混乱,不会改变。

其他回答

坦率地说,我们遇到的几乎所有东西在JAXP版本中都很好,所以我们总是排除xmlapi和xercesImpl。

还有一个选项没有在这里探讨:在Maven中声明Xerces依赖项为可选:

<dependency>
   <groupId>xerces</groupId>
   <artifactId>xercesImpl</artifactId>
   <version>...</version>
   <optional>true</optional>
</dependency>

基本上,这是为了迫使所有依赖者声明他们的Xerces版本,否则他们的项目将无法编译。如果他们想覆盖这种依赖关系,欢迎他们这样做,但这样他们就会拥有潜在的问题。

这为下游项目创造了强有力的激励:

做出积极的决定。他们是使用同一版本的Xerces还是使用其他版本?实际上测试它们的解析(例如通过单元测试)和类加载,并且不要打乱它们的类路径。

并非所有开发人员都会跟踪新引入的依赖关系(例如,mvn依赖关系:树)。这种做法将立即引起他们的注意。

它在我们的组织中运作得很好。在它推出之前,我们曾经生活在OP描述的同一个地狱里。

我的朋友很简单,这里有一个例子:

<dependency>
    <groupId>xalan</groupId>
    <artifactId>xalan</artifactId>
    <version>2.7.2</version>
    <scope>${my-scope}</scope>
    <exclusions>
        <exclusion>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
    </exclusion>
</dependency>

如果您想在终端(本例中为windows控制台)中检查maven树是否没有问题:

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r

我想你需要回答一个问题:

是否存在一个xerces*.jar,您的应用程序中的所有内容都可以使用它?

如果不是的话,你基本上就完蛋了,必须使用OSGI这样的工具,它允许你同时加载不同版本的库。请注意,它基本上将jar版本问题替换为类加载器问题。。。

如果存在这样的版本,您可以让您的存储库为所有类型的依赖项返回该版本。这是一个丑陋的黑客,最终会在类路径中多次使用相同的xerces实现,但比使用多个不同版本的xerce要好。

您可以排除对xerces的每个依赖项,并将其添加到您想要使用的版本中。

我想知道你是否可以写一些版本解析策略作为maven的插件。这可能是最好的解决方案,但如果可行的话,需要一些研究和编码。

对于运行时环境中包含的版本,您必须确保在考虑服务器的lib文件夹之前,将其从应用程序类路径中删除,或者首先考虑应用程序jar进行类加载。

所以总结一下:这是一片混乱,不会改变。

我知道这并不能准确地回答这个问题,但对于从谷歌进来的那些碰巧使用Gradle进行依赖管理的人来说:

我用Gradle解决了所有xerces/Java8问题,如下所示:

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