处理“Xerces地狱”在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,这意味着他们没有将正式版本上传到MavenCentral

  • Xerces曾经是作为一个罐子释放xerces.jar),但被分成两个罐子,一个包含API(xml-apis.jar),另一个包含这些API的实现(xercesImpl.jar)。许多较旧的Maven POM仍然声明依赖于xerces.jar。在过去的某个时候,Xerces也作为xmlParserAPIs.jar发布,一些较旧的POM也依赖于它。

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

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

  • 除了混乱之外,几乎所有的servlet容器(JBoss、Jetty、Glassfish、Tomcat等)都在一个或多个/lib文件夹中附带Xerces。

问题

冲突解决

由于上述原因中的某些-或所有-,许多组织发布和使用Xerce的自定义构建POM。如果您有一个小型应用程序并且只使用Maven Central,这并不是一个真正的问题,但对于Arti工厂或Nexus代理多个存储库(JBoss、Hibernate等)的企业软件来说,这很快就会成为一个问题:

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><provided>会很好,但您依赖的第三方代码可能适用于也可能不适用于您正在使用的JDK的JAXP中提供的版本。此外,您需要应对servlet容器中提供的Xerces jar。这为您留下了许多选择:您是否删除servlet版本并希望您的容器在JAXP版本上运行?是否最好保留servlet版本,并希望您的应用程序框架在servlet版本上运行?如果上面列出的一两个未解决的冲突设法进入你的产品(在大型组织中很容易发生),你很快就会发现自己陷入了类加载器的地狱,想知道类加载器在运行时选择的是哪个版本的Xerces,以及它是否会在Windows和Linux中选择相同的jar(可能不会)。

解决方案?

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

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


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

151504 次浏览

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

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

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

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

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

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

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

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

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

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

见:

自2013年2月20日以来,Maven Central有2.11.0 JAR(和源罐!)的Xerces!见位于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是与官方#0发行版中相同的JAR

然而,我找不到xml-schema-1.1-beta版本-它不可能是Mavenclassifier-ed版本,因为有额外的依赖项。

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

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

但是对于像OSGiJBoss模块这样的模块化框架,这不再是那么痛苦了。库可以独立地使用它们想要的任何库。

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

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

请注意,Java8正在进行模块化工作

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

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

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

还有另一个选项没有在这里讨论过:在Maven中声明Xerces依赖项为可选

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

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

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

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

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

在我们的组织里效果很好。在它推出之前,我们曾经生活在OP描述的同样的地狱里。

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

如果我是maven repo的老板,我会编写一个脚本来递归地删除xerces依赖项,并编写一个read me,说明此repo需要Java1.4。

任何实际中断的东西,因为它通过org.apache导入直接引用Xerces,都需要一个代码修复来将其提升到Java1.4级别(自2002年以来一直在这样做),或者通过认可的libs在JVM级别解决方案,而不是在maven中。

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

到命令行。

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