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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5447633/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 11:10:44  来源:igfitidea点击:

How to prevent xalan.jar that has META-INF\services\javax.xml.transform.TransformerFactory from taking over JDK 1.6 built in Xalan implementation?

javaxercesclassloaderxalan

提问by Eran Medan

Consider this code (based entirely on flying saucer's "getting started" code, their rights reserved):

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

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

Few facts:

几个事实:

  1. Running it standalone (calling main) with JDK 1.6 or 1.5 works perfectly (PDF is generated)
  2. But when loaded via a URLClassLoader from an existing web application it fails with this error:
  1. 使用 JDK 1.6 或 1.5 独立运行(调用 main)可以完美运行(生成 PDF)
  2. 但是当通过 URLClassLoader 从现有的 Web 应用程序加载时,它会失败并显示以下错误:
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

After looking for a while in the wrong place (for example, I created a child-first / parent-last class loader suspecting xalan / xerces jars, but it still fails), I finally narrowed down the root cause:

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

It seems that the web application that loads my code, has an old xalan.jar, specification version 1.2

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

I did a little test, I ran the code above as standalone (which worked fine before) but this time I added the xalan.jarfrom the web app to it's classpath, and bingo, the same error as in the web app scenario

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

So I inspected that old xalan.jar and wondered, what can cause the JVM to load it's old xalan implementation instead of the JDK's? after all my child-first class loader is also parent-last e.g. system in the middle, to say: searching the system classloader before the parent (to avoid loading parent overriden JDK jars, just like this case of the parent's xalan.jar overriding the JDK's xalan implementation)

所以我检查了那个旧的 xalan.jar 并想知道,什么会导致 JVM 加载它的旧 xalan 实现而不是 JDK 的?毕竟我的子级优先类加载器也是父级最后一个,例如中间的系统,也就是说:在父级之前搜索系统类加载器(为了避免加载父级覆盖的 JDK jar,就像父级的 xalan.jar 覆盖了JDK的xalan实现)

Then something cought my eyes - a file in: xalan.jar/META-INF/services/named javax.xml.transform.TransformerFactorywith this content:

然后有些东西让我眼前一亮——xalan.jar/META-INF/services/ 中的一个文件名为javax.xml.transform.TransformerFactory,内容如下:

org.apache.xalan.processor.TransformerFactoryImpl

So I imediately pressed Ctrl+Tin eclipse and looked for the full qualified name... only in xalan.jar!

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

Then I searched only for "TransformerFactoryImpl", and this is what the JDK has:

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

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

Easy to see the difference

很容易看出区别

So, if you read till here, my bottom line question is: How do I make my TransformerFactory use the JDK's implementation and not the old Xalan's one?(I can't remove that jar from the web app my code will be loaded from)

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

回答by Eran Medan

It seems the answer is simpler than I thought.

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

  1. In your classloader, add to the classpath (jar not required) this folder: /META-INF/services/

  2. In it, create a file named javax.xml.transform.TransformerFactory

  3. Edit it and set this as it's content to: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl

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

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

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

Thats it!

而已!

Why does it work? see this class used by Java for loading the Xalan implementation.

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

Notice that it seems de-facto a parent-last (or child-first) loader for that specific "META-INF" entry (the oposite of how regular Java class loaders work, e.g. parent-first / child-last) but feel free to correct me if I'm wrong

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

Snippet from javax.xml.datatype.FactoryFinder

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

        ...

回答by bmargulies

You should also note that you aren't requiredto use the SPI mechanism at all.

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

You can use http://download.oracle.com/javase/6/docs/api/javax/xml/xpath/XPathFactory.html#newInstance(java.lang.String, java.lang.String, java.lang.ClassLoader) to use a copy of xalan in your own isolated class loader once you know the name of the relevant class.

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

回答by Alain Pannetier

If you are developing a web application and thus can't set a system property, then the most direct way is to explicitly request the JDK transformer. Here is an example for the internal XSLTC transformer (has StAXSupport).

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

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

回答by eldrex

I'm using wildfly application server and 3rd jar which uses TransformerFactory.

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

Overriding TransformerFactory using file resources\META-INF\services\javax.xml.transform.TransformerFactory. Didn't work for me.

使用文件 resources\META-INF\services\javax.xml.transform.TransformerFactory 覆盖 TransformerFactory。没有对我来说有效。

When I looked to FactoryFinder implementation (JDK8u201). I found the following code fragment

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

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

Thus, solution was to set system property javax.xml.transform.TransformerFactory to com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.

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

Cheers

干杯