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

时间:2011-03-27 06:31:01

标签: 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();
        }
    }

很少有事实:

  1. 使用JDK 1.6或1.5独立运行(调用main)非常有效(生成PDF)
  2. 但是当从现有Web应用程序通过URLClassLoader加载时,它会因此错误而失败:
  3. 
    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的子级优先/父级最后一个类加载器,但它仍然失败),我最终缩小了根本原因:

    似乎加载我的代码的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 :< / p>

    org.apache.xalan.processor.TransformerFactoryImpl
    

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

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

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

    很容易看出差异

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

4 个答案:

答案 0 :(得分:7)

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

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

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

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

  5. 多数民众赞成!

    为什么会这样?请参阅Java用于加载Xalan实现的此类。

    请注意,事实上,对于特定的&#34; META-INF&#34;它似乎是父 - 最后(或子优先)加载器的事实。 entry(常规Java类加载器的工作方式,例如parent-first / child-last),但如果我错了,请随时纠正我

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

答案 1 :(得分:1)

您还应该注意,

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

我正在使用wildfly应用程序服务器和使用TransformerFactory的第三个jar。

使用文件资源\ 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。

欢呼