java fatal error_Java 解析 XML 时如何屏蔽掉 “[Fatal Error]” 的输出

我们在用 Java 解析 XML,当文档不是一个合法的 XML 时,可能会收到 [Fatal Error] 的控制台输出,即使把整个代码都 catch 住,仍然不能抑制住 [Fatal Error] 的信息输出。比如常见到这样的输出:

[Fatal Error] :1:1: Content is not allowed in prolog.

为什么不能禁掉它呢,本来 catch 了异常对程序已经有了很好的保护,想眼不见心不烦,但还是避之不及。

因为,因为这个 XML 解析器用 System.error.print() 输出来了,当然你可以用 System.setErr(PrintStream) 重定向掉错误输出,但不现实,波及面太大。我们需要找到源头,首先交代解决方案就是覆盖掉默认的 ErrorHandler。

看下这段 XML 解析代码:

import java.io.*;

import org.xml.sax.*;

import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilderFactory;

public class Test {

public static void main(String[] args) {

String xml = "abcd";

try {

DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()

Document doc = builder.parse(new InputSource(new ByteArrayInputStream(xml.getBytes)))

} catch(SaxParseException|IOException e) {

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

importjava.io.*;

importorg.xml.sax.*;

importorg.w3c.dom.Document;

importjavax.xml.parsers.DocumentBuilderFactory;

publicclassTest{

publicstaticvoidmain(String[]args){

Stringxml="abcd";

try{

DocumentBuilderbuilder=DocumentBuilderFactory.newInstance().newDocumentBuilder()

Documentdoc=builder.parse(newInputSource(newByteArrayInputStream(xml.getBytes)))

}catch(SaxParseException|IOExceptione){

}

}

}

上面的代码就会输出

[Fatal Error] :1:1: Content is not allowed in prolog.

如果 xml 的值是空字符串 "",输出为

[Fatal Error] :-1:-1: Premature end of file.

如果 xml 的值是 "ss&Emal",输出为

[Fatal Error] :1:12: The reference to entity "Email" must end with the ';' delimiter.

是不是对上面的错误输出很熟悉啊。

那就要看 DocumentBuilderFactory.newInstance.newDocumentBuilder 的两个过程,首先看 DocumentBuilderFactory.newInstance() 方法,见

看到它依次以四种方式找到 DocumentBuilderFactory 的实现类

系统属性 javax.xml.parsers.DocumentBuilderFactory

JRE 目录下的属性文件 "lib/jaxp.properties" 中的  javax.xml.parsers.DocumentBuilderFactory

SPI 形式,classpath 下加载 META-INF/services/javax.xml.parsers.DocumentBuilderFactory, 一般在 jar 包中

平台默认的 DocumentBuilderFactory 实例

默认的 JDK7 环境中 DocumentBuilderFactory.newInstance 是 com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl,

DocumentBuilderFactory.newInstance.newDocumentBuilder 是 com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl

比如在 xercesImpl-2.11.0.jar 包下就有文件 META-INF/services/javax.xml.parsers.DocumentBuilderFactory, 内容是

org.apache.xerces.jaxp.DocumentBuilderFactoryImpl, 相应的  DocumentBuilder 是 org.apache.xerces.jaxp.DocumentBuilderImpl

在实例化 DocumentBuilderImpl 时并没有给 DocumentBuilder 或 DOMParser 设置 ErrorHandler,而是在解析发现问题是设置上 ErrorHandler,然后输出错误,见

java-xml-error-handler-800x875.png?resize=800%2C875

上面设置了 DefaultErrorHandler, 点击 DefaultErrorHandler 看它的实现。它在 warning 和 error 时只打印错误,fatal 时除打印还抛出了异常。打印目的地是 System.err

但是我们可以设置一个 java.xml.parsers.DocumentBuilderFactory 系统属性从而使用自定义的 DocumentBuilderFactory 实现,在自己的 DocumentBuilderFactory 中初始化 DocumentBuilder 时设置自己的 ErrorHandler。

现在我们来实现自定义的 DocumentBuilderFactory:

CustomDocumentBuilderFactory.java

package helper;

import com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl;

import com.sun.org.apache.xml.internal.utils.DefaultErrorHandler;

import org.xml.sax.*;

import javax.xml.parsers.*;

public class CustomDocumentBuilderFactory extends DocumentBuilderFactoryImpl {

@Override

public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {

DocumentBuilder documentBuilder = super.newDocumentBuilder(); // 取回原本的 DocumentBuilder

documentBuilder.setErrorHandler(new DefaultErrorHandler() { // 只为替换掉 DefaultErrorHandler 的 fatalError() 方法

@Override

public void fatalError(SAXParseException exception) throws SAXException {

System.err.println("CustomDocumentBuilderFactory Caught the XML fatal error: " + exception.getMessage());

throw exception;

}

});

return documentBuilder;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

packagehelper;

importcom.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl;

importcom.sun.org.apache.xml.internal.utils.DefaultErrorHandler;

importorg.xml.sax.*;

importjavax.xml.parsers.*;

publicclassCustomDocumentBuilderFactoryextendsDocumentBuilderFactoryImpl{

@Override

publicDocumentBuildernewDocumentBuilder()throwsParserConfigurationException{

DocumentBuilderdocumentBuilder=super.newDocumentBuilder();// 取回原本的 DocumentBuilder

documentBuilder.setErrorHandler(newDefaultErrorHandler(){// 只为替换掉 DefaultErrorHandler 的 fatalError() 方法

@Override

publicvoidfatalError(SAXParseExceptionexception)throwsSAXException{

System.err.println("CustomDocumentBuilderFactory Caught the XML fatal error: "+exception.getMessage());

throwexception;

}

});

returndocumentBuilder;

}

}

然后重写获得 DocumentBuilder 部分代码为

System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "helper.CustomDocumentBuilderFactory");

DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

Document doc = builder.parse(new InputSource(new ByteArrayInputStream("".getBytes())));

1

2

3

System.setProperty("javax.xml.parsers.DocumentBuilderFactory","helper.CustomDocumentBuilderFactory");

DocumentBuilderbuilder=DocumentBuilderFactory.newInstance().newDocumentBuilder();

Documentdoc=builder.parse(newInputSource(newByteArrayInputStream("".getBytes())));

再次执行就只看到控制台的输出为

TestDocumentBuilderFactoryImpl Caught the XML fatal error: Premature end of file.

表明我们已经成功捕获到了 fataError 了,怎么输出这个错误是可控制的了。

在不同的环境下,例如 Tomcat 中可以去查看下默认的 DocumentBuilderFactory 实现,然后自定义的 Factory 就可以继承它,只为设置自己的 ErrorHandler,同时也保证了不破坏该环境下原有其他的行为。

比如在 Play2 中 DocumentBuilderFactory 实现是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl, DocumentBuilder 实现是 org.apache.xerces.jaxp.DocumentBuilderImpl,它们来自 xercesImpl 包,这时候我们自定义的 DocumentBuilderFactory 就可以继承自 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl, 其中设置自己的 ErrorHandler。

另外,我们也可以通过调用 DefaultErrorHandler 的另一个构造方法 DefaultErrorHandler(PrintWriter out) 来创造实例,通过 传递一个 PrintWriter 实例来接受输出,这样也能控制不把 Fatal Error 输出到控制台上。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值