Java 原生 DOM/SAX API 雖標準,但代碼冗長、異常處理繁瑣、命名空間支持弱。Dom4j 作為第三方庫,憑藉鏈式 API、XPath 原生支持、靈活的輸出格式化、優異的效能,長期佔據 Java XML 處理首選地位。本文通過完整示例,涵蓋讀取遍歷(List/Iterator 兩種風格)、創建寫入、XPath 查詢、命名空間處理、生產級優化配置。
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version> <!-- 最新穩定版 -->
</dependency>
核心類:
SAXReader — 解析器入口,支援文件、InputStream、URL、Reader。Document — 文檔根節點,getRootElement() 獲取根元素。Element — 元素節點,提供 elements()、element()、attributeValue()、elementText() 等便捷方法。DocumentHelper — 程序化創建文檔的工廠。XMLWriter + OutputFormat — 格式化輸出。<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="001">
<title>Harry Potter</title>
<author>J K. Rowling</author>
</book>
<book id="002">
<title>Learning XML</title>
<author>Erik T. Ray</author>
</book>
</books>
public static void parseWithList(String filePath) throws Exception {
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
Element root = document.getRootElement();
// 獲取所有子元素 List
List<Element> books = root.elements();
for (Element book : books) {
// 已知屬性名直接取值
String id = book.attributeValue("id");
System.out.println("id: " + id);
// 已知子元素名直接取文本
System.out.println("title: " + book.elementText("title"));
System.out.println("author: " + book.elementText("author"));
System.out.println();
}
}
優點:elements() 返回泛型 List,支援增強 for、Stream API、Lambda 處理。
public static void parseWithIterator(String filePath) throws Exception {
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
Element root = document.getRootElement();
Iterator<Element> it = root.elementIterator(); // 或 root.elementIterator("book")
while (it.hasNext()) {
Element book = it.next();
System.out.println("id: " + book.attributeValue("id"));
System.out.println("title: " + book.elementText("title"));
System.out.println("author: " + book.elementText("author"));
System.out.println();
}
}
優點:舊版本 Dom4j (1.x) 返回原始 Iterator,無泛型;大文檔時可邊讀邊處理釋放引用(需配合 SAX 事件模式)。
// 遞歸打印任意 XML 結構
public static void printTree(Element element, int depth) {
String indent = " ".repeat(depth);
System.out.println(indent + "<" + element.getName() + ">");
// 屬性
for (Attribute attr : element.attributes()) {
System.out.println(indent + " @" + attr.getName() + "=" + attr.getValue());
}
// 文本內容
String text = element.getTextTrim();
if (!text.isEmpty()) {
System.out.println(indent + " #text: " + text);
}
// 子元素遞歸
for (Element child : element.elements()) {
printTree(child, depth + 1);
}
}
public static void xpathDemo(Document doc) {
// 1. 選擇所有 book 的 title 文本
List<Node> titles = doc.selectNodes("//book/title/text()");
titles.forEach(n -> System.out.println("Title: " + n.getText()));
// 2. 選擇 id=002 的 book 元素
Element book002 = (Element) doc.selectSingleNode("//book[@id='002']");
if (book002 != null) {
System.out.println("Found: " + book002.elementText("title"));
}
// 3. 統計 book 數量
Number count = (Number) doc.selectObject("count(//book)");
System.out.println("Total books: " + count.intValue());
// 4. 命名空間查詢(需註冊前綴)
// Map<String, String> ns = Map.of("ns", "https://example.com/ns");
// doc.selectNodes("//ns:book", ns);
}
XPath 支援完整 1.0 標準:路徑、謂語、軸、函數(count, sum, contains, starts-with 等)。
public static void createXml(String outputPath) throws Exception {
// 1. 創建文檔對象
Document doc = DocumentHelper.createDocument();
// 2. 構建樹結構
Element books = doc.addElement("books");
Element book1 = books.addElement("book").addAttribute("id", "001");
book1.addElement("title").setText("Harry Potter");
book1.addElement("author").setText("J K. Rowling");
Element book2 = books.addElement("book").addAttribute("id", "002");
book2.addElement("title").setText("Learning XML");
book2.addElement("author").setText("Erik T. Ray");
// 3. 格式化輸出配置
OutputFormat format = OutputFormat.createPrettyPrint(); // 縮排、換行
format.setEncoding("UTF-8"); // 指定編碼
format.setIndent(" "); // 兩空格縮排
format.setNewlines(true); // 換行
// format.setTrimText(true); // 去除文本節點首尾空白
// 4. 寫入文件
try (FileOutputStream fos = new FileOutputStream(outputPath);
XMLWriter writer = new XMLWriter(fos, format)) {
writer.write(doc);
}
// 5. 寫入字串(用於網絡傳輸/日誌)
StringWriter sw = new StringWriter();
try (XMLWriter writer = new XMLWriter(sw, format)) {
writer.write(doc);
}
String xmlString = sw.toString();
}
輸出效果與示例 XML 完全一致,含 XML 聲明、縮排、UTF-8 編碼。
<root xmlns="https://default.ns" xmlns:ns="https://custom.ns">
<ns:item id="1">value</ns:item>
</root>
// 1. 解析時:預設命名空間不影響 element("name") 查找(需用 QName)
Element root = doc.getRootElement();
QName itemQName = QName.get("item", "https://custom.ns"); // 或者
// QName itemQName = new QName("item", Namespace.get("https://custom.ns"));
Element item = root.element(itemQName);
// 2. XPath 查詢:必須註冊前綴映射
Map<String, String> nsMap = new HashMap<>();
nsMap.put("def", "https://default.ns");
nsMap.put("ns", "https://custom.ns");
List<Node> items = doc.selectNodes("//ns:item", nsMap);
// 注意:無前綴的默認命名空間在 XPath 中必須給一個前綴(如 def)
| 場景 | 問題 | 解決方案 |
|---|---|---|
| 大文件 (>50MB) | DOM 模式全量載入導致 OOM | 使用 SAXReader + ElementHandler 推模式處理,或遷移至 StAX/木犀 |
| 高併發解析 | SAXReader 非線程安全 | 每線程 new SAXReader(),或使用 ThreadLocal 復用 |
| 外部實體攻擊 (XXE) | 默認解析 DTD/外部實體 | reader.setFeature("https://apache.org/xml/features/disallow-doctype-decl", true); reader.setFeature("https://xml.org/sax/features/external-general-entities", false); |
| 編碼亂碼 | 文件聲明 encoding 與實際不符 | 強制指定 reader.setEncoding("UTF-8");輸出時 format.setEncoding("UTF-8") |
| 頻繁創建 Document | 對象分配壓力 | 對象池復用 Document/Element(需手動 detach 清理) |
| 屬性值含特殊字符 (< & >) | 手動拼接 XML 導致注入 | 永遠用 element.setText() / addAttribute(),Dom4j 自動轉義 |
| 庫 | 模式 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| Dom4j | DOM | API 友好、XPath 強、格式化好 | 全量載入、維護緩慢 | 中小配置文件、報表生成、接口組裝 |
| JDOM2 | DOM | 純 Java、泛型完善 | 效能稍弱 | 教學、輕量項目 |
| XOM | DOM | 極度嚴格、防 XXE | 學習曲線陡 | 高安全需求 |
| StAX (JSR-173) | 流式 | 標準、低內存、雙向游標 | API 低級、手動管理狀態 | 大文件、高吞吐、遷移項目 |
| Woodstox | StAX 實現 | 快、穩、功能全 | 第三方依賴 | 生產級 StAX 首選 |
| Jackson XML | Data Binding | POJO 映射、注解驅動 | 靈活性弱、需 Schema | REST API、配置綁定 |
Dom4j 雖非最新技術,但憑藉優雅的 API、強大的 XPath、成熟的生態,仍是 Java XML 處理的「瑞士軍刀」。掌握 SAXReader 讀取、DocumentHelper 創建、selectNodes XPath 查詢、OutputFormat 格式化輸出四大核心能力,並針對大文件、XXE、編碼、併發做好防禦,即可應對 90% 的業務場景。新架構設計時,建議根據文件大小、性能要求、團隊熟悉度,在 Dom4j、StAX、Jackson XML 中選型,避免「拿著錘子找釘子」。