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

Posted

技术标签:

【中文标题】如何防止具有 META-INF\\services\\javax.xml.transform.TransformerFactory 的 xalan.jar 接管 Xalan 实现中内置的 JDK 1.6?【英文标题】:How to prevent xalan.jar that has META-INF\services\javax.xml.transform.TransformerFactory from taking over JDK 1.6 built in Xalan implementation?如何防止具有 META-INF\services\javax.xml.transform.TransformerFactory 的 xalan.jar 接管 Xalan 实现中内置的 JDK 1.6? 【发布时间】:2011-07-23 18:55:36 【问题描述】:

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

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();
        
    

几个事实:

    使用 JDK 1.6 或 1.5 独立运行(调用 main)可以完美运行(生成 PDF) 但是,当从现有 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 jars,但还是失败了),我终于缩小了根本原因:

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

我做了一个小测试,我将上面的代码作为独立的代码运行(之前运行良好),但这次我将 xalan.jar 从网络应用程序添加到它的类路径中,宾果游戏,与 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 的实现?(我无法从将加载我的代码的网络应用程序)

【问题讨论】:

我之前的相关问题:***.com/questions/5440395/…***.com/questions/5445511/…***.com/questions/5444246/… 我认为这也是相关的download.oracle.com/javase/6/docs/technotes/guides/standards 所以没有办法以编程方式做到这一点? 【参考方案1】:

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

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

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

    编辑它并将其设置为: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;
        

        ...

【讨论】:

即使我也面临同样的问题。我照你说的做了。我打开 xalan.jar 并将 meta-inf/service/javax.xml.transform.TransformerFactory 文件的内容更改为 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl 。现在 jboss 运行时出现很多错误。 Web 应用程序现在可以正常工作,但是 jboss 中有很多错误。我也无法使用数据库连接池。我怎样才能使两者都工作(正确运行jboss以及使变压器工厂工作) @Ashwin 您绝对不应该更改 xalan.jar,而是将名为 META-INF/service/javax.xml.transform.TransformerFactory 的文件添加到您的应用程序。不确定这是否有帮助,很难。 Java 小程序上的类加载器是什么? (我也有同样的问题)【参考方案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 的副本。

【讨论】:

这很有趣!我去看看,这可能就是我要找的答案 但是,如果他们没有提供委托,我如何强制第 3 方代码使用我的类加载器? :) 哦,哎呀,我的错。如果你必须重定向其他人的代码,你就会被 SPI 困住。【参考方案3】:

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

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

【讨论】:

【参考方案4】:

我正在使用 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。

干杯

【讨论】:

以上是关于如何防止具有 META-INF\services\javax.xml.transform.TransformerFactory 的 xalan.jar 接管 Xalan 实现中内置的 JDK 1.6?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Gradle 的 JAR 中的 META-INF/服务

ServiceLoader的使用

AOP

Dubbo面试

Spi,微内核与插件化

java中的SPI机制