如何防止具有 META-INF\services\javax.xml.transform.TransformerFactory 的 xalan.jar 接管 Xalan 实现中内置的 JDK 1.6?
考虑一下这段代码(完全基于飞碟的“入门”代码,保留他们的权利):
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)
- 但是当通过 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
在错误的地方寻找了一段时间后(例如,我创建了一个怀疑xalan / xerces jar的子优先/父最后的类加载器,但它仍然失败),我终于缩小了范围根本原因:
加载我的代码的 Web 应用程序似乎有一个旧的 xalan.jar,规范版本 1.2
我做了一些测试,我将上面的代码作为独立运行(之前运行良好) )但是这次我将 Web 应用程序中的 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 的吗?(我无法从要加载代码的 Web 应用程序中删除该 jar)
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:
- Running it standalone (calling main) with JDK 1.6 or 1.5 works perfectly (PDF is generated)
- But when loaded via a URLClassLoader from an existing web application it fails with this error:
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:
It seems that the web application that loads my code, has an old xalan.jar, specification version 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.jar from the web app to it's classpath, and bingo, the same error as in the web app scenario
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)
Then something cought my eyes - a file in: xalan.jar/META-INF/services/ named javax.xml.transform.TransformerFactory with this content:
org.apache.xalan.processor.TransformerFactoryImpl
So I imediately pressed Ctrl+T in eclipse and looked for the full qualified name... only in xalan.jar!
Then I searched only for "TransformerFactoryImpl", and this is what the JDK has:
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)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您正在开发 Web 应用程序,因此无法设置系统属性,那么最直接的方法是显式请求 JDK 转换器。
以下是内部 XSLTC 转换器(具有 StAXSupport)的示例。
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).
您还应该注意,您根本不需要使用 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 的副本。
You should also note that you aren't required to use the SPI mechanism at all.
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.
我正在使用 Wildfly 应用程序服务器和使用 TransformerFactory 的第 3 个 jar。
使用文件 resources\META-INF\services\javax.xml.transform.TransformerFactory 覆盖 TransformerFactory。不适合我。
当我查看 FactoryFinder 实现(JDK8u201)时。我发现以下代码片段
因此,解决方案是将系统属性 javax.xml.transform.TransformerFactory 设置为 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl。
干杯
I'm using wildfly application server and 3rd jar which uses TransformerFactory.
Overriding TransformerFactory using file resources\META-INF\services\javax.xml.transform.TransformerFactory. Didn't work for me.
When I looked to FactoryFinder implementation (JDK8u201). I found the following code fragment
Thus, solution was to set system property javax.xml.transform.TransformerFactory to com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.
Cheers
看来答案比我想象的要简单。
在您的类加载器中,将以下文件夹添加到类路径(不需要 jar):
/META-INF/services/
在其中创建一个名为
javax.xml.transform.TransformerFactory
编辑该文件并将其内容设置为:
com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl< /code>
就是这样!
为什么它有效?请参阅 Java 用于加载 Xalan 实现的此类。
请注意,对于特定的“META-INF”条目,它实际上似乎是一个父最后(或子优先)加载器(与常规 Java 类加载器的工作方式相反,例如父优先/子最后),但随意如果我错了
javax.xml.datatype.FactoryFinder
的代码片段,请纠正我It seems the answer is simpler than I thought.
In your classloader, add to the classpath (jar not required) this folder:
/META-INF/services/
In it, create a file named
javax.xml.transform.TransformerFactory
Edit it and set this as it's content to:
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.
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
Snippet from
javax.xml.datatype.FactoryFinder