Dealing with “Xerces hell” in Java/Maven?
在我的办公室里,仅仅提到Xerces这个词就足以激起开发者的杀伤力。粗略地看一下其他Xerces问题,似乎表明几乎所有Maven用户在某个时候都会被这个问题"触动"。不幸的是,了解这个问题需要一点关于行刑史的知识…
历史XECES是Java生态系统中使用最广泛的XML解析器。几乎每一个用Java编写的库或框架都使用XECES(如果不是直接的话)。
官方二进制文件中包含的Xercesjar直到今天还没有版本化。例如,Xerces2.11.0实现jar的名称是
xercesImpl.jar ,而不是xercesImpl-2.11.0.jar 。Xerces团队不使用Maven,这意味着他们不使用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 API和XerceImpl JAR的版本通常是不同的。例如,XML API的版本可能是1.3.03,XerceImpl的版本可能是2.8.0,即使两者都来自Xerces2.8.0。这是因为人们经常用XML API JAR实现的规范版本来标记它。这里有一个非常好的,但不完全的分解。
为了使问题复杂化,XECES是JRE中包含的Java API(JAXP)的引用实现中使用的XML解析器。实现类在
com.sun.* 名称空间下重新打包,这使得直接访问它们很危险,因为它们在某些JRE中可能不可用。但是,并非所有Xerces功能都通过java.* 和javax.* API公开;例如,没有任何API公开Xerces序列化。除了混乱之外,几乎所有servlet容器(jboss、jetty、glassfish、tomcat等)都在一个或多个
/lib 文件夹中随Xerces一起装运。
问题冲突解决
因为上面的一些原因,或者可能全部原因,很多组织在其POMS。如果您有一个小应用程序,并且只使用Maven Central,这实际上不是问题,但是对于Artifactory或Nexus代理多个存储库(JBoss、Hibernate等)的企业软件来说,这很快就会成为问题:
例如,A组织可能将
1 2 3 | <groupId>org.apache.xerces</groupId> xml-apis</artifactId> <version>2.9.1</version> |
同时,B组织可以发布与以下内容相同的
1 2 3 | <groupId>xml-apis</groupId> xml-apis</artifactId> <version>1.3.04</version> |
虽然B的
如上所述,jre在jaxp ri中带有xerces。虽然最好将所有Xerces-Maven依赖项标记为
我们已经尝试将所有Xerces Maven依赖项标记为
我们怎样才能最好地用Maven解决这个问题?我们是否必须对依赖项进行如此细粒度的控制,然后依赖于分层类加载?是否有某种方法可以全局排除所有Xerces依赖项,
有2.11.0罐(和源罐!)自2013年2月20日起,马文中心的Xerces!见Maven Central的Xerces。我想知道他们为什么没有解决https://issues.apache.org/jira/browse/xercesj-1454…
我曾经用过:
1 2 3 4 5 | <dependency> <groupId>xerces</groupId> xercesImpl</artifactId> <version>2.11.0</version> </dependency> |
所有的依赖关系都已经很好地解决了——甚至是正确的
最重要的(以及过去不明显的)是,Maven Central的罐子和官方的
但是,我找不到
坦率地说,我们遇到的几乎所有东西在JAXP版本中都可以正常工作,所以我们总是排除
您可以将MavenEnforcer插件与禁止的依赖规则一起使用。这将允许您禁止所有不想要的别名,只允许您想要的别名。如果违反这些规则,项目的Maven构建将失败。此外,如果此规则适用于企业中的所有项目,则可以将插件配置放入企业父POM中。
见:
- http://maven.apache.org/plugins/maven-enforcer-plugin/
- http://maven.apache.org/enforcer/enforcer-rules/banneddependences.html
我知道这并不能准确回答问题,但对于来自谷歌的PPL,恰好使用Gradle进行依赖性管理:
我设法解决了Gradle的所有Xerces/Java8问题:
1 2 3 4 | configurations { all*.exclude group: 'xml-apis' all*.exclude group: 'xerces' } |
我想你需要回答一个问题:
是否存在应用程序中所有内容都可以使用的xerces*.jar?
如果不是这样的话,你基本上是被拧了,必须使用OSGi之类的工具,这样你就可以同时加载不同版本的库。请注意,它基本上将JAR版本问题替换为类加载器问题…
如果存在这样的版本,您可以让您的存储库返回所有依赖项的版本。这是一个丑陋的黑客,最终会在类路径中多次使用相同的Xerces实现,但比使用多个不同版本的Xerces要好。
您可以排除对Xerces的每个依赖项,并将其添加到您要使用的版本中。
我想知道您是否可以编写某种版本解析策略作为Maven的插件。这可能是最好的解决方案,但如果可行的话,需要进行一些研究和编码。
对于运行时环境中包含的版本,您必须确保在考虑服务器的lib文件夹之前,将其从应用程序类路径中删除,或者先考虑应用程序jar进行类加载。
所以总结一下:这是一个烂摊子,不会改变。
这里还有一个尚未探讨的选项:将maven中的xerces依赖项声明为可选:
1 2 3 4 5 6 | <dependency> <groupId>xerces</groupId> xercesImpl</artifactId> <version>...</version> <optional>true</optional> </dependency> |
基本上,这样做的目的是强制所有依赖项声明它们的Xerces版本,否则它们的项目将无法编译。如果他们想覆盖这种依赖关系,我们欢迎他们这样做,但是他们将拥有潜在的问题。
这为下游项目创造了强有力的激励:
- 做出积极的决定。他们是使用相同版本的Xerces还是使用其他版本的Xerces?
- 实际上测试它们的解析(例如,通过单元测试)和类加载,同时不要使它们的类路径混乱。
并非所有开发人员都跟踪新引入的依赖项(例如,使用
它在我们的组织中工作得很好。在它被介绍之前,我们曾经生活在同一个地狱里。
您应该首先进行调试,以帮助识别XML地狱的级别。在我看来,第一步是增加
1 2 3 | -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 |
到命令行。如果可以,那么就开始排除库。如果没有,则添加
1 | -Djaxp.debug=1 |
到命令行。
每一个Maven项目都应该停止依赖于Xerces,他们可能不是真的。XML API和IMPL自1.4以来一直是Java的一部分。不需要依赖XECES或XML API,就像说您依赖Java或Swing。这是含蓄的。
如果我是一个Maven RePo的老板,我会编写一个脚本来递归删除XRESE依赖项,并写一个Read Me,称这个RPO需要Java 1.4。
任何真正中断,因为它通过XORACE直接引用Or.ApHACK导入需要一个代码修复,使其达到Java 1.4级(并且自2002以来已经完成)或通过认可的LIBS在JVM级的解决方案,而不是在Maven中。
我的朋友很简单,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 | <dependency> <groupId>xalan</groupId> xalan</artifactId> <version>2.7.2</version> <scope>${my-scope}</scope> <exclusions> <exclusion> <groupId>xml-apis</groupId> xml-apis</artifactId> </exclusion> </exclusions> </dependency> |
如果要签入终端(本例中为Windows控制台),则Maven树没有问题:
1 | mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r |
显然,
这对我很有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <dependency> <groupId>xerces</groupId> xercesImpl</artifactId> <version>2.11.0</version> <exclusions> <exclusion> <groupId>xerces</groupId> xml-apis</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>xml-apis</groupId> xml-apis</artifactId> <version>1.4.01</version> </dependency> |
除了排除之外,模块化依赖性将有帮助。
对于一个平面类加载(独立应用程序)或半层次(jboss as/eap 5.x),这是一个问题。
但是对于OSGi和JBoss模块这样的模块化框架,这不再是什么痛苦了。图书馆可以独立使用他们想要的任何图书馆。
当然,仍然建议只使用一个实现和版本,但是如果没有其他方法(使用更多libs的额外特性),那么模块化可能会节省您的时间。
jboss模块的一个很好的例子就是jboss as 7/eap 6/wildfly 8,它最初是为它开发的。
模块定义示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?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正在进行模块化的工作,但AFIK主要是对JRE本身进行模块化,而不确定它是否适用于应用程序。