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

Era*_*dan 4 java xerces classloader xalan

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

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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

几个事实:

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

Caused by: org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.
    at org.apache.xerces.dom.AttrNSImpl.setName(Unknown Source)
    at org.apache.xerces.dom.AttrNSImpl.(Unknown Source)
    at org.apache.xerces.dom.CoreDocumentImpl.createAttributeNS(Unknown Source)
    at org.apache.xerces.dom.ElementImpl.setAttributeNS(Unknown Source)
    at org.apache.xml.utils.DOMBuilder.startElement(DOMBuilder.java:307)
    ... 19 more

在错误的地方寻找一段时间后(例如,我创建了一个怀疑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
Run Code Online (Sandbox Code Playgroud)

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

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

com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
Run Code Online (Sandbox Code Playgroud)

很容易看出差异

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

Era*_*dan 8

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

  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;
        }

        ...
Run Code Online (Sandbox Code Playgroud)