Apache Xerces 快速入门与集成示例

Apache Xerces(Xerces-J 2.12.2)快速入门与集成示例:以完整 Maven 工程演示 DOM 遍历、SAX 事件流与 XSD 校验,覆盖命名空间处理、错误行列号定位与 JVM 堆参数验证,代码与回显均经实测。

Apache XercesApache Xerces2026/10/12

Apache Xerces 快速入门与集成示例

一、入门须知

Apache Xerces(Xerces-J)是 Java 生态最基础的 XML 解析器之一,实测最新版为 2.12.2(Maven Central,2022-01-27 发布),支持 JDK 8 及以上(本文实测 OpenJDK 17)。集成只需一个依赖:

坐标版本说明
xerces:xercesImpl2.12.2解析器实现本体
xml-apis:xml-apis1.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 即来自这套机制。两点实测经验:

  1. error() 回调若只打印不抛出,validate() 会继续跑完并正常返回,结论必须以错误计数为准(实测误报过"校验通过");
  2. 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??? 恢复为正常中文)。

九、参考链接