在Java生态中处理XML数据,DOM4J是一个非常流行且易用的开源库。它采用面向树的解析模型,将整个XML文档加载为内存中的Document对象,开发者可以通过简单的API像操作集合一样访问节点、属性以及文本内容。相比JDK自带的DOM接口,DOM4J的语法更加简洁;相比SAX这种基于事件的流式解析,它允许随机读写,更适合配置文件和中小型报文的处理场景。

DOM4J的基础依赖与Document对象构建
要在项目中使用DOM4J,首先需要将对应的jar包引入classpath。以Maven工程为例,通常在pom.xml里声明dom4j依赖即可。DOM4J底层依赖JAXP的解析器,但对外屏蔽了繁琐的工厂类调用,统一通过SAXReader来完成文档读取。理解Document对象是使用DOM4J的第一站,它代表整个XML树的根容器,所有Element和Attribute都是它的后代节点。
下面的代码展示了如何从文件系统加载一个XML并获取根元素。注意SAXReader的read方法可以接收File、InputStream或者URL,这在处理不同来源的配置时非常灵活。如果XML中包含声明编码,DOM4J会自动按照声明解码,不需要手动指定字符集,避免了常见的乱码问题。
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
public class Dom4jBasic {
public static void main(String[] args) {
SAXReader reader = new SAXReader();
try {
// 从本地文件读取XML构建Document对象
Document doc = reader.read(new File("C:\\config\\app.xml"));
Element root = doc.getRootElement();
System.out.println("根节点名称: " + root.getName());
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
上述示例中路径使用了Windows风格的反斜杠,在Java字符串中反斜杠本身需要转义写成双反斜杠,但在实际文件系统中它依然是C:\config\app.xml这样的路径结构。如果XML体积不大,这种一次性读入内存的方式代码量最少,也最容易调试。不过要留意,当文件超过几百兆时,Document会占用大量堆空间,此时应考虑换用基于事件的解析方案。
遍历元素与读取属性的实用写法
拿到根元素之后,真正的业务往往是要提取特定子节点的值或者遍历同构的列表。DOM4J提供了element(String name)获取单个子节点,以及elements()返回List<Element>的方式。对于属性,Element对象自带attributeValue(String name)方法,可以直接拿到字符串结果,不必像原生DOM那样先取Node再转型。
假设我们有一份用户列表的XML,每个user节点带id属性并包含name和age子元素。下面的示例演示了如何安全地遍历并打印内容。这里使用了elementTextTrim方法来读取子元素文本,它会自动去掉首尾空白,比先getElement再getText再trim要省事很多。
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.List;
public class Dom4jTraverse {
public static void main(String[] args) throws Exception {
SAXReader reader = new SAXReader();
Document doc = reader.read("C:\\data\\users.xml");
Element root = doc.getRootElement();
List<Element> userList = root.elements("user");
for (Element user : userList) {
String id = user.attributeValue("id");
String name = user.elementTextTrim("name");
String age = user.elementTextTrim("age");
System.out.println("id=" + id + ", name=" + name + ", age=" + age);
}
}
}
在实际项目中,XML结构可能嵌套较深,如果每一层都用硬编码名称去取,代码会变得脆弱。一种改进思路是写一个通用的递归方法,根据节点类型做不同处理;另一种是使用DOM4J支持的XPath表达式,直接用路径字符串定位节点。XPath适合结构复杂但查询模式固定的场景,它能把多层循环压缩成一行selectNodes调用,显著提升可读性。
需要提醒的是,当XML带有命名空间时,直接用element("book")可能返回null,因为真实名称变成了带前缀的限定名。此时要在SAXReader上调用setFeature关闭命名空间感知,或者在XPath里注册命名空间映射。很多初学者在这个坑上耗费过时间,其实只要在读取前明确业务是否真的需要区分命名空间就能提前规避。
使用DOM4J生成并写出新的XML文档
解析只是单向操作,更多时候我们还需要把内存中的对象树写回文件,比如导出报表或生成请求报文。DOM4J通过DocumentHelper创建空Document,再利用addElement链式构建节点。写文件则依赖XMLWriter配合OutputFormat,可以指定缩进和编码,让生成的文件既机器可读也方便人眼检查。
以下代码从零创建一份包含书籍信息的XML,并输出到磁盘。OutputFormat.createPrettyPrint会让节点自动换行缩进,而setEncoding确保中文不会写成乱码实体。相比手动拼接字符串,这种方式不容易漏掉闭合标签,也方便后续再读取回来。
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import java.io.FileOutputStream;
public class Dom4jWrite {
public static void main(String[] args) throws Exception {
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement("library");
Element book = root.addElement("book");
book.addAttribute("id", "1");
book.addElement("title").setText("编程思想");
book.addElement("author").setText("Bruce");
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8");
XMLWriter writer = new XMLWriter(new FileOutputStream("C:\\out\\lib.xml"), format);
writer.write(doc);
writer.close();
}
}
在写出大批量数据时,建议复用同一个XMLWriter并分批flush,而不是累积完所有节点再一次性写盘,这样能把内存峰值降下来。另外如果目标系统对XML声明行有严格要求,可以通过format.setSuppressDeclaration控制是否输出<?xml version="1.0"?>。DOM4J默认会带声明,这在多数Web接口里没问题,但某些老旧交换协议可能要求纯节点内容。
综合来看,DOM4J在易用性和功能性之间取得了不错的平衡。掌握读取、遍历和写出这三组核心API,就能应付绝大多数Java项目里的XML处理任务。若后续遇到性能瓶颈,再结合XPath或切换解析模式也不迟,基础代码无需推倒重来。