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)
几个事实:
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,我的代码将被加载)
似乎答案比我想象的要简单.
在类加载器中,添加到类路径(jar not not)这个文件夹: /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;
}
...
Run Code Online (Sandbox Code Playgroud)