如何防止具有META-INF \ services \ javax.xml.transform.TransformerFactory的xalan.jar接管内置在Xalan实现中的JDK 1.6?

考虑一下这段代码(完全基于飞碟的“入门”代码,保留其权利):

package flyingsaucerpdf; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import org.xhtmlrenderer.pdf.ITextRenderer; public class PDFMaker { public static void main(String[] args) throws Exception { new PDFMaker().go(); } public void go() throws Exception { String inputFile = "sample.html"; String url = new File(inputFile).toURI().toURL().toString(); String outputFile = "firstdoc.pdf"; OutputStream os = new FileOutputStream(outputFile); ITextRenderer renderer = new ITextRenderer(); renderer.setDocument(url); renderer.layout(); renderer.createPDF(os); os.close(); } } 

几个事实:

  1. 使用JDK 1.6或1.5独立运行(调用main)可以完美运行(生成PDF)
  2. 但是当从现有Web应用程序通过URLClassLoader加载时,它会失败,并显示以下错误:

由以下原因引起:org.w3c.dom.DOMException:NAMESPACE_ERR:尝试以对名称空间不正确的方式创建或更改对象。
     at org.apache.xerces.dom.AttrNSImpl.setName(未知来源)
    在org.apache.xerces.dom.AttrNSImpl。(未知来源)
    在org.apache.xerces.dom.CoreDocumentImpl.createAttributeNS(未知来源)
    在org.apache.xerces.dom.ElementImpl.setAttributeNS(未知来源)
    在org.apache.xml.utils.DOMBuilder.startElement(DOMBuilder.java:307)
     ......还有19个

在错误的地方寻找一段时间后(例如,我创建了一个怀疑xalan / xerces jars的child-first / parent-last类加载器,但它仍然失败),我最终缩小了根本原因:

似乎加载我的代码的Web应用程序有一个旧的xalan.jar ,规范版本1.2

我做了一点测试,我将上面的代码作为独立运行(之前工作正常)但是这次我将web应用程序中的xalan.jar添加到了它的classpath和bingo,这与Web应用程序场景中的错误相同

所以我检查了旧的xalan.jar并想知道,是什么导致JVM加载旧的xalan实现而不是JDK? 毕竟我的子级第一类加载器也是父级 – 例如中间的系统,例如:在父级之前搜索系统类加载器(以避免加载父级重写JDK jar,就像父级的xalan.jar覆盖的情况一样) JDK的xalan实现)

然后我的眼睛就出现了一个问题 – 一个文件: xalan.jar / META-INF / services /命名为javax.xml.transform.TransformerFactory ,内容如下:

 org.apache.xalan.processor.TransformerFactoryImpl 

所以我在eclipse中按下Ctrl + T并查找完整的限定名称…仅在xalan.jar中

然后我只搜索“TransformerFactoryImpl”,这就是JDK所拥有的:

 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl 

很容易看出差异

所以,如果你读到这里,我的底线问题是: 我如何让我的TransformerFactory使用JDK的实现而不是旧的Xalan? (我无法从Web应用程序中删除该jar,我的代码将被加载)

似乎答案比我想象的要简单。

  1. 在你的类加载器中,添加到类路径(jar not not)这个文件夹: /META-INF/services/

  2. 在其中,创建一个名为javax.xml.transform.TransformerFactory的文件

  3. 编辑它并将其内容设置为: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl

而已!

它为什么有效? 请参阅Java用于加载Xalan实现的此类。

请注意,对于特定的“META-INF”条目(对于常规Java类加载器的工作方式,例如父级优先/子级最后),它似乎事实上是父级最后(或子级优先)加载器,但是感觉自由如果我错了,纠正我

来自javax.xml.datatype.FactoryFinder片段

  /* * Try to find provider using Jar Service Provider Mechanism * * @return instance of provider class if found or null */ private static Object findJarServiceProvider(String factoryId) throws ConfigurationError { String serviceId = "META-INF/services/" + factoryId; InputStream is = null; // First try the Context ClassLoader ClassLoader cl = ss.getContextClassLoader(); if (cl != null) { is = ss.getResourceAsStream(cl, serviceId); // If no provider found then try the current ClassLoader if (is == null) { cl = FactoryFinder.class.getClassLoader(); is = ss.getResourceAsStream(cl, serviceId); } } else { // No Context ClassLoader, try the current // ClassLoader cl = FactoryFinder.class.getClassLoader(); is = ss.getResourceAsStream(cl, serviceId); } if (is == null) { // No provider found return null; } ... 

您还应注意,根本不需要使用SPI机制。

您可以使用http://download.oracle.com/javase/6/docs/api/javax/xml/xpath/XPathFactory.html#newInstance(java.lang.String,java.lang.String,java.lang.ClassLoader )一旦知道了相关类的名称,就可以在自己的隔离类加载器中使用xalan的副本。

如果您正在开发Web应用程序,因此无法设置系统属性,那么最直接的方法是显式请求JDK转换器。 以下是内部XSLTC转换器(具有StAXSupport)的示例。

 TransformerFactory tf = TransformerFactory.newInstance( "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", null);