首页 文章

在Java / Maven中处理“Xerces hell”?

提问于
浏览
640

在我的办公室里,仅仅提到Xerces这个词就足以煽动开发者的凶悍愤怒 . 粗略地看一眼其他Xerces关于SO的问题,似乎表明几乎所有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 jarxerces.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是JDL中包含的Java API for XML Processing(JAXP)的参考实现中使用的XML解析器 . 实现类在 com.sun.* 名称空间下重新打包,这使得直接访问它们很危险,因为它们可能在某些JRE中不可用 . 但是,并非所有Xerces功能都通过 java.*javax.* API公开;例如,没有API公开Xerces序列化 .

  • 添加到令人困惑的混乱中,几乎所有的servlet容器(JBoss,Jetty,Glassfish,Tomcat等)都在一个或多个 /lib 文件夹中附带Xerces .

问题

冲突解决

对于某些 - 或者可能是所有 - 上述原因,许多组织在其POM中发布和使用Xerces的自定义构建 . 如果你有一个小应用程序并且只使用Maven Central,这不是一个真正的问题,但它很快成为企业软件的问题,其中Artifactory或Nexus代理多个存储库(JBoss,Hibernate等):

xml-apis proxied by Artifactory

例如,组织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 将被包含为已解析的依赖项:

resolved dependencies with multiple xml-apis

Classloader Hell

如上所述,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-apisxercesxercesImplxmlParserAPIs 等),因此难以强制执行(特别是对于大型团队) . 此外,我们的第三方库/框架可能无法在JAXP版本或servlet容器提供的版本上运行 .

How can we best address this problem with Maven? Do we have to exercise such fine-grained control over our dependencies, and then rely on tiered classloading? Is there some way to globally exclude all Xerces dependencies, and force all of our frameworks/libs to use the JAXP version?


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

11 回答

  • 97

    自2013年2月20日起,Maven Central有2.11.0 JARs (and source JARs!) xerces!见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是 the same JAR as in the official Xerces-J-bin.2.11.0.zip distribution .

    然而,我无法找到 xml-schema-1.1-beta 版本 - 由于其他依赖项,它不能是Maven classifier -ed版本 .

  • 41

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

    坦率地说,差不多我们遇到的所有东西都适用于JAXP版本,所以 we always exclude xml-apisxercesImpl .

  • 6

    您可以将maven enforcer插件与禁止的依赖关系规则一起使用 . 这将允许您禁止所有您不想要的别名,并且只允许您想要的那些别名 . 违反时,这些规则将导致项目的maven构建失败 . 此外,如果此规则适用于企业中的所有项目,则可以将插件配置放在公司父pom中 .

    看到:

  • 4

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

    Does there exist a xerces.jar that everything in your application can live with?*

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

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

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

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

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

    所以把它包起来:这是一团糟,不会改变 .

  • 57

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

    <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>
                </exclusions>
            </dependency>
    

    如果你想检查你的maven树没有问题的终端(这个例子的Windows控制台):

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

    除了排除外,有用的是模块化依赖 .

    使用一个平面类加载(独立应用程序)或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本身,不确定它是否适用于应用程序 .

  • 16

    每个maven项目都应该停止取决于xerces,它们可能不是真的 . 自1.4以来,XML API和Impl一直是Java的一部分 . 没有必要依赖于xerces或XML API,就像说你依赖于Java或Swing . 这是隐含的 .

    如果我是maven repo的老板,我会编写一个脚本来递归删除xerces依赖项,并写一个读取我说这个repo需要Java 1.4 .

    由于它通过org.apache导入直接引用Xerces而实际中断的任何东西需要一个代码修复,以使其达到Java 1.4级别(并且自2002年以来已完成)或通过已签名的库而不是maven的JVM级别的解决方案 .

  • 24

    我知道这并没有完全回答这个问题,但是对于来自谷歌的人来说恰巧使用Gradle进行依赖管理:

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

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

    还有另一个选项尚未在此处探讨:将Maven中的Xerces依赖项声明为可选:

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

    基本上这样做是为了强制所有家属声明他们的Xerces版本或他们的项目不会编译 . 如果他们想要覆盖这种依赖关系,欢迎他们这样做,但他们将拥有潜在的问题 .

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

    • 做出积极的决定 . 他们使用相同版本的Xerces还是使用别的东西?

    • 实际上测试他们的解析(例如通过单元测试)和类加载,以及不弄乱他们的类路径 .

    并非所有开发者都保持跟踪新引入的依赖项(例如,使用 mvn dependency:tree ) . 这种方法将立即引起他们的注意 .

    它在我们的组织中运作良好 . 在它介绍之前,我们过去常常和OP描述的地狱生活在一起 .

  • 2

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

    到命令行 .

相关问题