繁体   English   中英

在 Java/Maven 中处理“Xerces 地狱”?

[英]Dealing with “Xerces hell” in Java/Maven?

在我的办公室,仅仅提到 Xerces 这个词就足以激起开发人员的杀气。 粗略地看一下关于 SO 的其他 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 也依赖于它。

  • 将 jar 部署到 Maven 存储库的人分配给 xml-apis 和 xercesImpl jar 的版本通常不同。 例如,xml-apis 的版​​本可能是 1.3.03,而 xercesImpl 的版本可能是 2.8.0,即使两者都来自 Xerces 2.8.0。 这是因为人们经常用它实现的规范版本来标记 xml-apis jar。 还有就是这是一个非常不错的,但不完全击穿这里

  • 更复杂的是,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 中发布和使用 Xerces 的自定义构建。 如果您有一个小型应用程序并且只使用 Maven Central,这并不是真正的问题,但是对于 Artifactory 或 Nexus 代理多个存储库(JBoss、Hibernate 等)的企业软件来说,这很快就会成为一个问题:

Artifactory 代理的 xml-apis

例如,组织 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 不知道它们是同一个工件,因为它们具有不同的groupId 因此,它无法执行冲突解决,并且两个jar都将作为已解决的依赖项包含在内:

使用多个 xml-apis 解决依赖关系

类加载器地狱

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

解决方案?

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

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


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

自 2013 年 2 月 20 日起,Maven Central 中有 2.11.0 JAR (和源 JAR!) Xerces! 请参阅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.4.01

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

但是,我找不到xml-schema-1.1-beta版本 - 由于其他依赖项,它不能是 Maven classifier版本。

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

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

看:

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

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

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

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

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

如果不是,你基本上被搞砸了,必须使用像 OSGI 这样的东西,它允许你同时加载不同版本的库。 请注意,它基本上用类加载器问题替换了 jar 版本问题......

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

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

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

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

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

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

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

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

这为下游项目提供了强大的动力:

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

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

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

您应该首先进行调试,以帮助确定您的 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

到命令行。

每个 Maven 项目都应该停止依赖 xerces,他们可能真的没有。 XML API 和 Impl 从 1.4 开始就成为 Java 的一部分。 无需依赖 xerces 或 XML API,就像说您依赖 Java 或 Swing 一样。 这是隐含的。

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

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

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

对于单一的类加载(独立应用程序)或半分层(JBoss AS/EAP 5.x),这是一个问题。

但是有了像OSGiJBoss Modules这样的模块化框架,这就不再那么痛苦了。 图书馆可以独立使用他们想要的任何图书馆。

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

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

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

显然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>

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

<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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM