Apache Xerces 快速入门与集成示例
Apache Xerces(Xerces-J 2.12.2)快速入门与集成示例:以完整 Maven 工程演示 DOM 遍历、SAX 事件流与 XSD 校验,覆盖命名空间处理、错误行列号定位与 JVM 堆参数验证,代码与回显均经实测。
Apache Xerces 快速入门与集成示例
一、入门须知
Apache Xerces(Xerces-J)是 Java 生态最基础的 XML 解析器之一,实测最新版为 2.12.2(Maven Central,2022-01-27 发布),支持 JDK 8 及以上(本文实测 OpenJDK 17)。集成只需一个依赖:
| 坐标 | 版本 | 说明 |
|---|---|---|
| xerces:xercesImpl | 2.12.2 | 解析器实现本体 |
| xml-apis:xml-apis | 1.4.01 | 随 xercesImpl 自动引入的 API 包 |
它提供三条使用路径:DOM(全量树)、SAX(事件流)、XSD 校验(javax.xml.validation)。特别说明:把 xercesImpl 放入 classpath 后,JAXP 的默认工厂会自动从 JDK 内置实现(com.sun.org.apache.xerces.internal.*)切换为独立版(org.apache.xerces.*),本文代码在两种形态下均实测运行。
二、完整工程准备
2.1 目录结构
/root/xerces-demo
├── pom.xml
└── src/main
├── java/com/example/xerces
└── resources
2.2 pom.xml(全文)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>xerces-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
2.3 样例 XML(books.xml)
<?xml version="1.0" encoding="UTF-8"?>
<books xmlns="http://www.example.com/books">
<book id="101">
<title>Java核心技术</title>
<author>周志明</author>
<price>129.00</price>
</book>
<book id="102">
<title>Effective Java</title>
<author>Joshua Bloch</author>
<price>108.50</price>
</book>
</books>
2.4 样例 Schema(books.xsd)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.example.com/books"
targetNamespace="http://www.example.com/books"
elementFormDefault="qualified">
<xs:element name="books" type="tns:BooksType"/>
<xs:complexType name="BooksType">
<xs:sequence>
<xs:element name="book" type="tns:BookType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="BookType">
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="author" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
<xs:attribute name="id" type="xs:int" use="required"/>
</xs:complexType>
</xs:schema>
2.5 构建
cd /root/xerces-demo
mvn -q package
mvn -q dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt
export LC_ALL=C.UTF-8
三、DOM 解析遍历
src/main/java/com/example/xerces/DomDemo.java(全文):
package com.example.xerces;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class DomDemo {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
System.out.println("DocumentBuilderFactory 实现: " + factory.getClass().getName());
factory.setNamespaceAware(true); // 默认 false,关闭时按命名空间查找将得到 0 个节点
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("src/main/resources/books.xml"));
Element root = doc.getDocumentElement();
System.out.println("根元素: " + root.getTagName());
NodeList books = root.getElementsByTagNameNS("*", "book");
System.out.println("book 节点数: " + books.getLength());
for (int i = 0; i < books.getLength(); i++) {
Element book = (Element) books.item(i);
String id = book.getAttribute("id");
String title = book.getElementsByTagNameNS("*", "title").item(0).getTextContent();
String author = book.getElementsByTagNameNS("*", "author").item(0).getTextContent();
String price = book.getElementsByTagNameNS("*", "price").item(0).getTextContent();
System.out.println("book[" + i + "] id=" + id + " | " + title + " | " + author + " | " + price);
}
}
}
运行与实测回显:
java -cp target/classes:$(cat /tmp/cp.txt) com.example.xerces.DomDemo
DocumentBuilderFactory 实现: org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
根元素: books
book 节点数: 2
book[0] id=101 | Java核心技术 | 周志明 | 129.00
book[1] id=102 | Effective Java | Joshua Bloch | 108.50
第一行说明默认工厂已被 classpath 上的 xercesImpl 接管。
四、SAX 事件流解析
src/main/java/com/example/xerces/SaxDemo.java(全文):
package com.example.xerces;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.XMLReader;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class SaxDemo {
public static void main(String[] args) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
// 传入参数 ns 开启命名空间感知;默认 false,此时 uri 与 localName 均为空串
if (args.length > 0 && "ns".equals(args[0])) {
factory.setNamespaceAware(true);
}
System.out.println("SAXParserFactory 实现: " + factory.getClass().getName()
+ " (namespaceAware=" + factory.isNamespaceAware() + ")");
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
reader.setContentHandler(new ContentHandler() {
@Override
public void setDocumentLocator(Locator locator) {
System.out.println("[setDocumentLocator] systemId=" + locator.getSystemId());
}
@Override
public void startDocument() {
System.out.println("[startDocument]");
}
@Override
public void endDocument() {
System.out.println("[endDocument]");
}
@Override
public void startPrefixMapping(String prefix, String uri) {
}
@Override
public void endPrefixMapping(String prefix) {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < atts.getLength(); i++) {
sb.append(" ").append(atts.getQName(i)).append("=").append(atts.getValue(i));
}
System.out.println("[startElement] uri=" + uri + " localName=" + localName + " qName=" + qName + sb);
}
@Override
public void endElement(String uri, String localName, String qName) {
System.out.println("[endElement] " + qName);
}
@Override
public void characters(char[] ch, int start, int length) {
String text = new String(ch, start, length).trim();
if (!text.isEmpty()) {
System.out.println("[characters] " + text);
}
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) {
}
@Override
public void processingInstruction(String target, String data) {
}
@Override
public void skippedEntity(String name) {
}
});
reader.parse("src/main/resources/books.xml");
}
}
运行与实测回显(ns 模式,截取片段):
java -cp target/classes:$(cat /tmp/cp.txt) com.example.xerces.SaxDemo ns
SAXParserFactory 实现: org.apache.xerces.jaxp.SAXParserFactoryImpl (namespaceAware=true)
[setDocumentLocator] systemId=file:///root/xerces-demo/src/main/resources/books.xml
[startDocument]
[startElement] uri=http://www.example.com/books localName=books qName=books
[startElement] uri=http://www.example.com/books localName=book qName=book id=101
[startElement] uri=http://www.example.com/books localName=title qName=title
[characters] Java核心技术
[endElement] title
...
[endElement] books
[endDocument]
五、XSD 校验
5.1 校验主程序
src/main/java/com/example/xerces/ValidateDemo.java(全文):
package com.example.xerces;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
public class ValidateDemo {
public static void main(String[] args) throws Exception {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
System.out.println("SchemaFactory 实现: " + schemaFactory.getClass().getName());
Schema schema = schemaFactory.newSchema(new File("src/main/resources/books.xsd"));
Validator validator = schema.newValidator();
String[] files = {
"src/main/resources/books.xml",
"src/main/resources/books-invalid.xml",
"src/main/resources/books-badwf.xml"
};
for (String file : files) {
System.out.println("==== 校验 " + file + " ====");
validator.reset();
final int[] errors = {0}; // error() 回调不抛异常时校验器会继续跑完,须自行计数
validator.setErrorHandler(new ErrorHandler() {
@Override
public void warning(SAXParseException e) {
System.out.println("[WARNING] 行 " + e.getLineNumber() + " 列 " + e.getColumnNumber() + ": " + e.getMessage());
}
@Override
public void error(SAXParseException e) {
errors[0]++;
System.out.println("[ERROR] 行 " + e.getLineNumber() + " 列 " + e.getColumnNumber() + ": " + e.getMessage());
}
@Override
public void fatalError(SAXParseException e) throws SAXParseException {
errors[0]++;
System.out.println("[FATAL] 行 " + e.getLineNumber() + " 列 " + e.getColumnNumber() + ": " + e.getMessage());
throw e;
}
});
try {
validator.validate(new StreamSource(new File(file)));
if (errors[0] == 0) {
System.out.println("结果: 校验通过");
} else {
System.out.println("结果: 校验失败,共报告 " + errors[0] + " 处错误");
}
} catch (SAXParseException e) {
System.out.println("结果: 校验失败(致命错误已中断解析)");
}
}
}
}
5.2 违规样例(books-invalid.xml)
第二个 book 缺少必需的 id 属性,price 写成了非数字:
<?xml version="1.0" encoding="UTF-8"?>
<books xmlns="http://www.example.com/books">
<book id="101">
<title>Java核心技术</title>
<author>周志明</author>
<price>129.00</price>
</book>
<book>
<title>Effective Java</title>
<author>Joshua Bloch</author>
<price>not-a-number</price>
</book>
</books>
5.3 运行回显
java -cp target/classes:$(cat /tmp/cp.txt) com.example.xerces.ValidateDemo
合法文件、非法文件与格式错误文件(books-badwf.xml,根元素未闭合)的完整实测回显:
SchemaFactory 实现: org.apache.xerces.jaxp.validation.XMLSchemaFactory
==== 校验 src/main/resources/books.xml ====
结果: 校验通过
==== 校验 src/main/resources/books-invalid.xml ====
[ERROR] 行 8 列 11: cvc-complex-type.4: Attribute 'id' must appear on element 'book'.
[ERROR] 行 11 列 36: cvc-datatype-valid.1.2.1: 'not-a-number' is not a valid value for 'decimal'.
[ERROR] 行 11 列 36: cvc-type.3.1.3: The value 'not-a-number' of element 'price' is not valid.
结果: 校验失败,共报告 3 处错误
==== 校验 src/main/resources/books-badwf.xml ====
[FATAL] 行 9 列 1: XML document structures must start and end within the same entity.
结果: 校验失败(致命错误已中断解析)
非法 XML 校验失败的报错要点:cvc-complex-type.4 对应缺少必需属性,cvc-datatype-valid.1.2.1 与 cvc-type.3.1.3 对应数据类型不匹配,行列号均精确到出错位置。
六、扩展实践
6.1 命名空间处理
两个实测默认值必须记住:DocumentBuilderFactory 与 SAXParserFactory 的 namespaceAware 默认都是 false。关闭时的实测表现:
- DOM:
getElementsByTagNameNS("*", "book")返回 0 个节点(实测从 2 变 0); - SAX:startElement 回调的 uri 与 localName 为空串,属性列表出现
xmlns=http://www.example.com/books。
带命名空间文档的推荐写法:
factory.setNamespaceAware(true);
// DOM 中按命名空间查找
NodeList books = root.getElementsByTagNameNS("*", "book");
NodeList titles = book.getElementsByTagNameNS("http://www.example.com/books", "title");
6.2 ErrorHandler 定位行列号
所有校验错误都封装在 SAXParseException 中,getLineNumber()/getColumnNumber()/getSystemId() 可直接定位问题。第五章回显中 [ERROR] 行 8 列 11、[FATAL] 行 9 列 1 即来自这套机制。两点实测经验:
- error() 回调若只打印不抛出,validate() 会继续跑完并正常返回,结论必须以错误计数为准(实测误报过"校验通过");
- fatalError() 必须抛出异常才能中断解析,吞掉它会导致解析器继续读入乱掉的数据。
DOM 路径的异常同样携带行列信息,实测对 books-badwf.xml 执行 DocumentBuilder.parse():
[Fatal Error] books-badwf.xml:9:1: XML document structures must start and end within the same entity.
捕获 SAXParseException:
systemId = file:/root/xerces-demo/src/main/resources/books-badwf.xml
行=9 列=1
message = XML document structures must start and end within the same entity.
6.3 通过扩展属性外部指定 Schema
不依赖文档内 xsi:schemaLocation 声明时,可用 Xerces 扩展属性注入(注意键值格式"命名空间 URI + 空格 + 路径",路径需绝对路径):
XMLReader reader = new org.apache.xerces.parsers.SAXParser();
reader.setFeature("http://xml.org/sax/features/validation", true);
reader.setFeature("http://apache.org/xml/features/validation/schema", true);
reader.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation",
"http://www.example.com/books /root/xerces-demo/src/main/resources/books.xsd");
实测对 books-invalid.xml 校验回显与 Validator 路径完全一致(三处 cvc 错误)。安全加固再叠加一个特性,实测报错原文:
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
[Fatal Error] :2:10: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
解析被拦截,异常原文: 行=2 列=10 DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
七、JVM 堆验证
7.1 参数配置
XML 解析是内存密集型操作,建议 256–512 MB 起步。配置位置:/etc/profile.d/xerces-maven.sh(新建,登录 shell 生效;不要写 ~/.bashrc,非交互 shell 会提前 return 导致实测不生效):
export MAVEN_OPTS="-Xms256m -Xmx512m"
java 直跑时用 JAVA_OPTS 风格传参:
export JAVA_OPTS="-Xms256m -Xmx512m"
java $JAVA_OPTS -cp target/classes:$(cat /tmp/cp.txt) com.example.xerces.BigXmlDom /root/xerces-demo/big-books.xml 20
7.2 大文件实测(38.2 MB / 40 万条)
先生成大文件(BigXmlGen 代码见配套工程):
生成完成: /root/xerces-demo/big-books.xml 条数=400000 大小=38.2 MB 耗时=505 ms
512 MB 堆下 DOM 全量解析实测(重复 20 次压测):
解析目标: /root/xerces-demo/big-books.xml 大小=38.2 MB
JVM maxMemory(-Xmx)=512 MB totalMemory=258 MB
解析完成: 根元素=books book 节点数=400000 重复次数=20 总耗时=20386 ms 解析后堆占用约=455 MB maxMemory=512 MB
256 MB 堆下同场景实测 OOM:
[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.6.4:java (default-cli) on project xerces-demo: An exception occurred while executing the Java class. Java heap space -> [Help 1]
结论:38.2 MB XML 的 DOM 表示约需 455 MB 堆,接近 XML 体积的 12 倍;上限 512 MB 是必需配置,更大文件请改用 SAX 流式解析。
7.3 参数生效验证
后台启动解析进程后:
jps -lv
7857 com.example.xerces.BigXmlDom -Xms256m -Xmx512m
ps -ef | grep [j]ava
root 7857 7848 99 14:46 ? 00:00:06 java -Xms256m -Xmx512m -cp target/classes:/root/.m2/repository/xerces/xercesImpl/2.12.2/xercesImpl-2.12.2.jar:/root/.m2/repository/xml-apis/xml-apis/1.4.01/xml-apis-1.4.01.jar com.example.xerces.BigXmlDom /root/xerces-demo/big-books.xml 20
注意:MAVEN_OPTS 只对 Maven 进程(含 exec:java)生效;mvn -version 在 Maven 3.6.3 下不打印该变量,验证以 jps/ps 实测为准。
八、常见问题
Q1:引入 xercesImpl 后程序行为和以前不一样了?
classpath 上的 xercesImpl 会通过 META-INF/services 声明接管 JAXP 默认工厂(实测 DocumentBuilderFactory.newInstance() 从 com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl 变为 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl)。需固定实现时显式设置系统属性。
Q2:DOM 查不到任何节点?
九成是没开命名空间感知。实测默认(false)时 getElementsByTagNameNS 返回 0;factory.setNamespaceAware(true) 后返回 2。SAX 同理。
Q3:校验结果明明有 ERROR 却显示通过? error() 回调不抛异常时 validate() 正常返回(实测踩坑)。按第五章代码维护错误计数,或让回调直接抛出 SAXParseException。
Q4:报 cvc-complex-type.4 / cvc-datatype-valid.1.2.1 是什么意思?
分别是"缺少必需属性"与"值不符合简单类型"。实测样例:Attribute 'id' must appear on element 'book'、'not-a-number' is not a valid value for 'decimal',对照 XSD 修正文档即可。
Q5:OutOfMemoryError 怎么调参? 先确认 -Xmx 实际生效(jps -lv),再对照 7.2 节的内存倍率估算(DOM 约为 XML 体积 10–12 倍)。实测 38.2 MB 文件在 256 MB 堆下 OOM、512 MB 下峰值 455 MB。超大规模文件建议 SAX。
Q6:控制台中文变问号?
进程 locale 非 UTF-8。export LC_ALL=C.UTF-8 后重跑(实测从 ????Java??? 恢复为正常中文)。
九、参考链接
- Xerces2 Java 官方站点:https://xerces.apache.org/xerces2-j/
- 特性(features)官方清单:https://xerces.apache.org/xerces2-j/features.html
- 属性(properties)官方清单:https://xerces.apache.org/xerces2-j/properties.html
- Maven Central 构件页:https://central.sonatype.com/artifact/xerces/xercesImpl
- 源码仓库:https://github.com/apache/xerces-j