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

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

历史

  • Xerces是Java生态系统中使用最广泛的XMLparsing器。 几乎每个使用Java编写的库或框架都以某种身份使用Xerces(即使不是直接传递)。

  • 包括在官方二进制文件中的Xercesjar子至今还没有版本。 例如,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( xml-apis.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是JRE中包含的用于XML处理的Java API的参考实现(JAXP)中使用的XMLparsing器。 实现类在com.sun.*命名空间下重新打包,这使得直接访问这些实现类非常危险,因为它们可能在某些JRE中不可用。 但是,并非所有的Xercesfunction都通过java.*javax.* API公开; 例如,没有暴露Xerces序列化的API。

  • 除此之外,几乎所有的servlet容器(JBoss,Jetty,Glassfish,Tomcat等)都在Xerces的一个或多个/lib文件夹中提供。

问题

解决冲突

对于上面的一些原因或者全部原因,许多组织在他们的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>

尽pipeB的jar比A的jar版本更低,但Maven并不知道它们是相同的,因为它们具有不同的groupId 。 因此,它不能执行冲突解决,并且两个jar子将被包括为解决的依赖关系:

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

类加载器地狱

如上所述,JRE在JAXP RI中与Xerces一起发货。 尽pipe将所有的Xerces Maven依赖项标记为<exclusion><provided>是很好的,但您所依赖的第三方代码可能会使用或不使用您所使用的JDK的JAXP中提供的版本。 另外,你的servlet容器中有Xercesjar子可以与之抗衡。 这给你留下了许多select:你是否删除了servlet版本,并希望你的容器在JAXP版本上运行? 离开servlet版本更好吗,并希望您的应用程序框架在servlet版本上运行? 如果上面列出的一个或两个未解决的冲突导致您的产品陷入困境(容易在大型组织中发生),那么您很快就会发现自己处于classloader地狱,想知道classloader在运行时select哪个版本的Xerces,以及是否它将在Windows和Linux中select相同的jar(可能不是)。

解决scheme?

我们已经尝试将所有的Xerces Maven依赖关系标记为<provided>或者作为一个<exclusion> ,但是这是很难实现的(特别是对于一个大团队),假设这些工件有很多别名(xml-apis,xerces,xercesImpl, xmlParserAPI等)。 此外,我们的第三方库/框架可能不能运行在JAXP版本或由servlet容器提供的版本上。

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


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

自2013年2月20日起,Maven中心有2.11.0个xerces的JAR (和源JARs!) ! 参见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版本相同

我无法findxml-schema-1.1-beta版本 – 因为附加的依赖关系,它不能成为Maven classifier版本。

坦率地说,我们所遇到的几乎所有的东西都可以在JAXP版本中正常工作,所以我们总是排除xml-apis和xercesImpl。

你可以使用maven执行者插件与禁止的依赖关系规则。 这将允许你禁止所有你不想要的别名,只允许你想要的别名。 这些规则违反了你的项目的maven版本。 此外,如果此规则适用于企业中的所有项目,则可以将插件configuration置于公司父项目中。

看到:

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

我设法摆脱所有与Gradle的xerces / Java8问题是这样的:

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

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

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

如果没有,你基本上是搞砸了,将不得不使用类似OSGI的东西,它允许你同时加载不同版本的库。 被警告,它基本上用类加载器的问题replacejar版本问题…

如果存在这样的版本,您可以使您的存储库为各种依赖项返回该版本。 这是一个丑陋的黑客,最终会在你的类path中多次执行相同的xerces实现,但是比拥有多个不同版本的xerces更好。

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

我不知道你是否可以编写某种版本parsing策略作为maven的插件。 这可能是最好的解决scheme,但如果在所有可行的需要一些研究和编码。

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

所以要结束它:这是一个混乱,这不会改变。

还有另外一个选项在这里还没有被探讨:在Maven中声明Xerces依赖关系是可选的

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

基本上这是强制所有的家属声明他们的版本的Xerces或他们的项目将不会编译。 如果他们想要推翻这种依赖关系,那么他们可以这样做,但是他们会拥有潜在的问题。

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

  • 做出积极的决定。 他们是否使用相同版本的Xerces或使用其他的东西?
  • 实际上testing他们的parsing(如通过unit testing)和类加载,以及不要混乱他们的类path。

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

它在我们的组织工作得很好。 在引入之前,我们曾经和OP描述的地狱一样。

除了排除,除了模块化依赖之外,还有什么帮助。

使用一个扁平类加载(独立应用程序)或半分层(JBoss AS / EAP 5.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模块更简单快捷。 虽然缺less某些function,但对于大多数(大部分)受到一个供应商控制的项目来说,这是足够的,并且允许惊人的快速启动(由于解除了依赖关系)。

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

您应该首先进行debugging,以帮助确定您的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,他们可能不会真的。 从1.4开始,XML API和Impl就成为了Java的一部分。 不需要依赖xerces或XML API,就像说你依靠Java或Swing一样。 这是隐含的。

如果我是一个maven回购的老板,我会写一个脚本来recursion地删除xerces的依赖关系,并写一个读我说,这个回购需要Java 1.4。

任何因为通过org.apache导入直接引用Xerces而实际上中断的事情,都需要一个代码修复,以使其达到Java 1.4级别(自2002年以来已经完成),或者通过支持的库(而不是maven)来支持JVM级别的解决scheme。

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

如果你想检查terminal(这个例子中的Windows控制台),你的Maven树没有问题:

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