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

问题描述 投票:670回答:11

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

History

  • Xerces是Java生态系统中使用最广泛的XML解析器。几乎每个用Java编写的库或框架都以某种身份使用Xerces(传递,如果不是直接的话)。
  • 包括在official binaries中的Xerces罐子直到今天还没有版本化。例如,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),但被分成两个罐子,一个包含API(xml-apis.jar),另一个包含那些API(xercesImpl.jar)的实现。许多较旧的Maven POM仍然声明对xerces.jar的依赖。在过去的某个时刻,Xerces也被发布为xmlParserAPIs.jar,一些较老的POM也依赖于它。
  • 分配给xml-apis和xercesImpl的版本由那些将其jar部署到Maven存储库的人通常是不同的。例如,xml-apis可能是1.3.03版本,而xercesImpl可能是2.8.0版本,即使两者都来自Xerces 2.8.0。这是因为人们经常使用它实现的规范版本来标记xml-apis jar。这个here有一个非常好但不完整的故障。
  • 更复杂的是,Xerces是包含在JRE中的Java API for XML Processing(JAXP)的参考实现中使用的XML解析器。实现类在com.sun.*命名空间下重新打包,这使得直接访问它们很危险,因为它们可能在某些JRE中不可用。但是,并非所有Xerces功能都通过java.*javax.* API公开;例如,没有API公开Xerces序列化。
  • 添加到令人困惑的混乱中,几乎所有servlet容器(JBoss,Jetty,Glassfish,Tomcat等)都在其一个或多个/lib文件夹中附带Xerces。

Problems

Conflict Resolution

对于上述某些原因(或许是全部原因),许多组织在其POM中发布和使用Xerces的自定义构建。如果你有一个小应用程序并且只使用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都将作为已解析的依赖项包含在内:

Classloader Hell

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

Solutions?

我们已经尝试将所有Xerces Maven依赖项标记为<provided><exclusion>,但这很难强制执行(特别是对于一个大型团队),因为这些工件有很多别名(xml-apisxercesxercesImplxmlParserAPIs等)。此外,我们的第三方库/框架可能无法在JAXP版本或servlet容器提供的版本上运行。

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


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

java maven classloader dependency-management xerces
11个回答
103
投票

自2013年2月20日起,Maven Central的Xerces有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版本,因为它有额外的依赖性。


2
投票

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

1
投票

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

<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

62
投票

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


42
投票

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

看到:


27
投票

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

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

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

16
投票

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

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

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

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

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

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

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

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


6
投票

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

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

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

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

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

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

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


6
投票

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

到命令行。


5
投票

每个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年以来完成)或通过支持的libs而不是maven中的JVM级别的解决方案。


2
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.