【问题标题】:Dealing with "Xerces hell" in Java/Maven?在 Java/Maven 中处理“Xerces hell”?
【发布时间】:2012-07-25 12:40:25
【问题描述】:

在我的办公室里,仅仅提到 Xerces 这个词就足以激起开发者的杀气。粗略浏览一下关于 SO 的其他 Xerces 问题似乎表明几乎所有 Maven 用户在某个时候都被这个问题“触动”了。不幸的是,理解这个问题需要对 Xerces 的历史有一点了解......

历史

  • Xerces 是 Java 生态系统中使用最广泛的 XML 解析器。几乎每个用 Java 编写的库或框架都在一定程度上使用了 Xerces(如果不是直接传递,也可以传递)。

  • official binaries 中包含的 Xerces jar 直到今天还没有版本控制。例如,Xerces 2.11.0 实现 jar 被命名为 xercesImpl.jar 而不是 xercesImpl-2.11.0.jar

  • Xerces 团队 does not use Maven,这意味着他们没有 将正式版本上传至Maven Central

  • Xerces 曾经是 released as a single jar (xerces.jar),但被拆分为两个 jar,一个包含 API (xml-apis.jar),另一个包含这些 API 的实现 (xercesImpl.jar)。许多旧的 Maven POM 仍然声明对xerces.jar 的依赖。在过去的某个时候,Xerces 也以xmlParserAPIs.jar 发布,一些较旧的 POM 也依赖它。

  • 那些将 jar 部署到 Maven 存储库的人分配给 xml-apis 和 xercesImpl jar 的版本通常是不同的。例如,xml-apis 可能被赋予 1.3.03 版本,而 xercesImpl 可能被赋予 2.8.0 版本,即使两者都来自 Xerces 2.8.0。这是因为人们经常用它实现的规范版本来标记 xml-apis jar。这个here 有一个很好但不完整的细分。

  • 更复杂的是,Xerces 是用于 Java API for XML Processing (JAXP) 的参考实现的 XML 解析器,包含在 JRE 中。实现类在 com.sun.* 命名空间下重新打包,这使得直接访问它们很危险,因为它们在某些 JRE 中可能不可用。但是,并非所有 Xerces 功能都通过 java.*javax.* API 公开;例如,没有公开 Xerces 序列化的 API。

  • 更令人困惑的是,几乎所有 servlet 容器(JBoss、Jetty、Glassfish、Tomcat 等)都在其一个或多个 /lib 文件夹中随附 Xerces。

问题

冲突解决

出于上述某些(或全部)原因,许多 组织在他们的 POM。如果您有一个小型应用程序并且只使用 Maven Central,这并不是一个真正的问题,但它很快就会成为 Artifactory 或 Nexus 代理多个存储库(JBoss、Hibernate 等)的企业软件的问题:

例如,组织 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比A的jar的版本低,但是Maven不知道 他们是同一个神器,因为他们有不同的 groupIds。因此,它不能执行冲突解决和两者 jars 将作为已解析的依赖项包含在内:

类加载器地狱

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

解决方案?

我们已尝试将所有 Xerces Maven 依赖项标记为 &lt;provided&gt;&lt;exclusion&gt;,但鉴于工件具有如此多的别名(xml-apis、@ 987654354@、xercesImplxmlParserAPIs 等)。此外,我们的第三方库/框架可能无法在 JAXP 版本或 servlet 容器提供的版本上运行。

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


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

【问题讨论】:

  • 感谢这个详细的问题。我不明白 xerces 团队的动机。我想他们会为那里的产品感到自豪并乐于使用它,但当前的 xerces 和 maven 状态是可耻的。即便如此,即使对我来说毫无意义,他们也可以做他们想做的事。我想知道 sonatype 的家伙是否有任何建议。
  • 这可能是题外话,但这可能是我见过的更好的帖子。与问题更相关的是,您所描述的是我们可以遇到的最痛苦的问题之一。伟大的倡议!
  • @TravisSchneeberger 大部分复杂性是因为 Sun 选择在 JRE 本身中使用 Xerces。你很难为此责怪 Xerces 人。
  • 通常我们尝试通过反复试验找到满足所有依赖库的 Xerces 版本,如果不可能,则重构为 WAR 以将应用程序拆分为单独的 WAR(单独的类加载器)。这个工具(我写的)通过允许查询 jars 和类的类路径来帮助理解 jhades.org 发生的事情 - 它也适用于服务器尚未启动的情况
  • 如果您在 windows 中从 git bash 启动 servicemix 时遇到此错误,请快速评论:改为从“正常”cmd 启动它。

标签: java maven classloader dependency-management xerces


【解决方案1】:

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

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

如果不是这样,你基本上就完蛋了,不得不使用 OSGI 之类的东西,它允许你同时加载不同版本的库。请注意,它基本上用类加载器问题替换了 jar 版本问题...

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

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

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

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

总结一下:这是一团糟,不会改变。

【讨论】:

  • 不同ClassLoader加载的同一个jar中的同一个类仍然是ClassCastException(在所有标准容器中)
  • 没错。这就是我写的原因:请注意,它基本上用类加载器问题替换了 jar 版本问题
【解决方案2】:

坦率地说,我们遇到的几乎所有东西都可以使用 JAXP 版本,所以我们总是排除xml-apisxercesImpl

【讨论】:

  • 你能为此添加一个 pom.xml sn-p 吗?
  • 当我尝试这个时,我得到 JavaMelody 和 Spring 在运行时抛出 java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal
  • 添加到 David Moles 的响应中——我已经看到六个传递依赖项需要 ElementTraversal。 Spring 和 Hadoop 中最常见的各种东西。
  • 如果您收到 java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal 尝试将 xml-apis 1.4.01 添加到您的 pom(并排除所有其他依赖版本)
  • ElementTraversal 是 Xerces 11 中添加的新类,可在 xml-apis:xml-apis:1.4.01 依赖项中使用。因此,您可能需要手动将类复制到您的项目或使用整个依赖项,这会导致类加载器中的类重复。但是在 JDK9 中,这个类被包含在特性中,你可能需要删除 dep。
【解决方案3】:

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

见:

【讨论】:

    【解决方案4】:

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

    我用过:

    <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 classifier-ed 版本。

    【讨论】:

    • 虽然xml-apis:xml-apis:1.4.01xml-apis:xml-apis:2.0.2xml-apis:xml-apis:2.0.2非常令人困惑?见search.maven.org/…
    • 这很令人困惑,但这是由于第三方上传了非版本化的 Xerces jar,就像 justingarrik 在他的帖子中所说的那样。 xml-apis 2.9.1 与 1.3.04 相同,因此从这个意义上说,1.4.01 比 1.3.04 更新(并且数值更大)。
    • 如果您的 pom.xml 中同时包含 xercesImpl 和 xml-apis,请务必删除 xml-apis 依赖项!否则 2.0.2 会抬起丑陋的脑袋。
    【解决方案5】:

    除了排除之外,有帮助的是模块化依赖项。

    使用一个平面类加载(独立应用程序)或semi-hierarchical (JBoss AS/EAP 5.x) 这是一个问题。

    但是对于像OSGiJBoss 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 模块更简单、更快。虽然缺少某些功能,但对于大多数(大部分)由一个供应商控制并允许惊人的快速启动(由于并行依赖解决)的项目来说已经足够了。

    请注意,有一个 modularization effort underway for Java 8,但 AFAIK 主要是为了模块化 JRE 本身,不确定它是否适用于应用程序。

    【讨论】:

    • jboss 模块是关于静态模块化的。它与 OSGi 必须提供的运行时模块化几乎没有关系——我会说它们是相互补充的。不过,这是一个不错的系统。
    • *补充而不是恭维
    【解决方案6】:

    我知道这并不能完全回答这个问题,但是对于来自谷歌的 ppl 碰巧使用 Gradle 进行依赖管理:

    我设法摆脱了 Gradle 的所有 xerces/Java8 问题,如下所示:

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

    【讨论】:

    • 很好,使用 maven,您需要大约 4000 行 XML 才能做到这一点。
    • 这并没有解决问题。对 Android-Gradle 用户的任何其他提示?
    • @teknopaul XML 纯粹用于配置。 Groovy 是一种高级编程语言。有时,您可能希望使用 XML 的明确性而不是 groovy 的魔力。
    【解决方案7】:

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

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

    这基本上是强制所有依赖项声明他们的 Xerces 版本,否则他们的项目将无法编译。如果他们想覆盖这种依赖关系,欢迎他们这样做,但是他们将承担潜在的问题。

    这为下游项目创造了强大的动力:

    • 做出积极的决定。他们是使用相同版本的 Xerces 还是使用其他东西?
    • 实际测试他们的解析(例如通过单元测试)和类加载,以及不要弄乱他们的类路径。

    并非所有开发人员都会跟踪新引入的依赖项(例如使用 mvn dependency:tree)。这种方法会立即引起他们的注意。

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

    【讨论】:

    • 我应该在版本元素中直接使用点-点-点,还是需要使用像 2.6.2 这样的真实版本?
    • @chrisinmtown 真实版本。
    【解决方案8】:

    每个 maven 项目都应该停止依赖于 xerces,但它们可能并非真的如此。 XML API 和 Impl 自 1.4 以来一直是 Java 的一部分。无需依赖 xerces 或 XML API,就像说您依赖 Java 或 Swing。这是隐含的。

    如果我是 maven repo 的老板,我会编写一个脚本来递归删除 xerces 依赖项,并写一篇自述文件说这个 repo 需要 Java 1.4。

    由于通过 org.apache 导入直接引用 Xerces 而实际中断的任何内容都需要代码修复以将其提升到 Java 1.4 级别(并且自 2002 年以来已经完成)或通过认可的库在 JVM 级别解决方案,而不是在 maven 中。

    【讨论】:

    • 在执行您详述的重构时,您还需要在 Java 文件和配置的文本中搜索包名和类名。您会发现开发人员已将 Impl 类的 FQN 放在 Class.forName 和类似结构使用的常量字符串中。
    • 这假定所有 SAX 实现都做同样的事情,这是不正确的。 xercesImpl 库允许 java.xml.parser 库缺少的配置选项。
    【解决方案9】:

    您应该首先进行调试,以帮助确定您的 XML 地狱级别。在我看来,第一步是添加

    -Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
    -Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
    -Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
    

    到命令行。如果可行,则开始排除库。如果没有,则添加

    -Djaxp.debug=1
    

    到命令行。

    【讨论】:

      【解决方案10】:

      显然xerces:xml-apis:1.4.01 不再位于 maven 中心,但是 xerces:xercesImpl:2.11.0 引用的内容。

      这对我有用:

      <dependency>
        <groupId>xerces</groupId>
        <artifactId>xercesImpl</artifactId>
        <version>2.11.0</version>
        <exclusions>
          <exclusion>
            <groupId>xerces</groupId>
            <artifactId>xml-apis</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
      <dependency>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
        <version>1.4.01</version>
      </dependency>
      

      【讨论】:

      • 对我来说似乎很重要:repo1.maven.org/maven2/xml-apis/xml-apis/1.4.01/… 最后修改时间 2011-08-20?
      • 当然,id 为xml-apis/xml-apis,但传递依赖是xerces/xml-apis,这就是为什么我的配置明确排除xerces/xml-apis,而是使用你正确指出的那个在中心.
      【解决方案11】:

      朋友说的很简单,这里举个例子:

      <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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-17
        相关资源
        最近更新 更多