4

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

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:试图以不正确的命名空间方式创建或更改对象。
    在 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 更多

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

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

我做了一个小测试,我将上面的代码作为独立的代码运行(之前运行良好),但是这次我将 web 应用程序中的xalan.jar添加到它的类路径和宾果游戏中,与 web 应用程序场景中的错误相同

所以我检查了那个旧的 xalan.jar 并想知道,什么会导致 JVM 加载它的旧 xalan 实现而不是 JDK 的?毕竟我的 child-first 类加载器也是 parent-last eg system in the middle,说:在父级之前搜索系统类加载器(以避免加载父级覆盖的 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)

4

4 回答 4

8

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

  1. 在您的类加载器中,将此文件夹添加到类路径(不需要 jar):/META-INF/services/

  2. 在其中创建一个名为javax.xml.transform.TransformerFactory

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

而已!

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

请注意,对于特定的“META-INF”条目(与常规 Java 类加载器的工作方式相反,例如 parent-first/child-last),它似乎实际上是 parent-last(或 child-first)加载器,但请随意如果我错了,纠正我

片段来自 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;
        }

        ...
于 2011-03-27T09:05:20.907 回答
2

您还应该注意,您根本不需要使用 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 的副本。

于 2011-03-27T13:41:16.263 回答
2

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

TransformerFactory tf = TransformerFactory.newInstance(
        "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", null);
于 2016-02-08T05:56:24.003 回答
0

我正在使用 wildfly 应用程序服务器和使用 TransformerFactory 的 3rd jar。

使用文件 resources\META-INF\services\javax.xml.transform.TransformerFactory 覆盖 TransformerFactory。没有为我工作。

当我查看 FactoryFinder 实现(JDK8u201)时。我找到了以下代码片段

String systemProp = ss.getSystemProperty(factoryId);
if (systemProp != null) {
    dPrint("found system property, value=" + systemProp);
    return newInstance(type, systemProp, null, true);
}

因此,解决方案是将系统属性 javax.xml.transform.TransformerFactory 设置为 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl。

干杯

于 2019-05-02T08:18:52.270 回答