在我的办公室里,仅仅提到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。投票/观看/贡献这个问题,让我们一劳永逸地解决这个问题。


当前回答

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

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

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

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

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

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

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

其他回答

每个maven项目都应该停止依赖xerces,他们可能并没有真正依赖xerce。自1.4以来,XMLAPI和Impl一直是Java的一部分。不需要依赖于xerces或XMLAPI,就像说依赖于Java或Swing一样。这是含蓄的。

如果我是一个maven repo的老板,我会编写一个脚本递归地删除xerces依赖关系,并编写一个readme,说明这个repo需要Java1.4。

任何由于通过org.apache导入直接引用Xerces而导致实际中断的东西都需要一个代码修复,以将其提升到Java1.4级别(自2002年以来一直如此)或通过认可的libs(而不是maven)在JVM级别解决方案。

除了排除之外,模块化依赖关系会有所帮助。

对于一个平面类加载(独立应用程序)或半分层(JBossAS/EAP5.x),这是一个问题。

但对于OSGi和JBoss Modules这样的模块化框架,这不再是那么痛苦了。图书馆可以独立使用他们想要的任何图书馆。

当然,最好还是只使用一个实现和版本,但如果没有其他方法(使用更多库中的额外功能),那么模块化可能会节省您的时间。

JBoss模块的一个很好的例子自然是JBoss AS 7/EAP 6/WildFly 8,它最初是为其开发的。

模块定义示例:

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.jboss.msc">
    <main-class name="org.jboss.msc.Version"/>
    <properties>
        <property name="my.property" value="foo"/>
    </properties>
    <resources>
        <resource-root path="jboss-msc-1.0.1.GA.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="org.jboss.logging"/>
        <module name="org.jboss.modules"/>
        <!-- Optional deps -->
        <module name="javax.inject.api" optional="true"/>
        <module name="org.jboss.threads" optional="true"/>
    </dependencies>
</module>

与OSGi相比,JBoss模块更简单、更快。虽然缺少某些特性,但对于大多数(大部分)由一个供应商控制的项目来说,它已经足够了,并且允许惊人的快速启动(由于并行依赖关系解决)。

注意,Java8正在进行模块化工作,但AFAIK主要是模块化JRE本身,不确定它是否适用于应用程序。

自2013年2月20日以来,Maven Central有2.11.0个Xerces JAR(和源JAR!)!参见Maven Central的Xerces。我想知道他们为什么还没有解决https://issues.apache.org/jira/browse/XERCESJ-1454...

我使用过:

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

所有依赖关系都得到了很好的解决,甚至是正确的xml-apis-1.401!

最重要的是(过去并不明显)——Maven Central中的JAR与Xerces-J-bin.2.11.0.zip官方发行版中的JAR相同。

然而,我找不到xml-schema-11-beta版本,因为附加的依赖性,它不能是Maven分类器ed版本。

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

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

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

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

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

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

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

您可以使用带有禁用依赖规则的maven enforcer插件。这将允许您禁止所有不需要的别名,只允许您需要的别名。如果违反这些规则,项目的maven构建将失败。此外,如果此规则适用于企业中的所有项目,则可以将插件配置放在企业父pom中。

see:

http://maven.apache.org/plugins/maven-enforcer-plugin/http://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html