Apache Commons Digester 快速入门与集成示例
以 Maven 工程演示 Apache Commons Digester 的快速上手与集成:从 XML 解析出对象列表,并给出属性与子元素混合映射的图书目录完整可跑示例。
Apache Commons Digester 快速入门与集成示例
目标:用 Maven 工程演示 Apache Commons Digester 的快速上手与工程集成,从最简单的 XML 解析出对象列表开始,再到属性与子元素混合映射的完整示例。全部命令与回显已在 Ubuntu 22.04(JDK 17 + Maven 3.6.3)实测通过。
一、环境信息
本章创建一个带 Commons Digester 依赖的 Maven 工程,作为后续所有示例的运行载体。先建目录骨架:
mkdir -p /opt/digester-quick/src/main/java/net/quzuo/demo
mkdir -p /opt/digester-quick/src/main/resources
cd /opt/digester-quick
该组命令无输出。写入 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>net.quzuo.demo</groupId>
<artifactId>digester-quick</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-digester3</artifactId>
<version>3.2</version>
</dependency>
</dependencies>
</project>
逐项说明:commons-digester3:3.2 一个坐标即会带入 cglib、commons-beanutils、commons-logging(及 cglib 依赖的 asm)全部传递依赖;maven.compiler.source/target 指定按 Java 17 编译;project.build.sourceEncoding 强制源码按 UTF-8 读取。验证依赖:
mvn dependency:tree
实测回显(输出节选,实测首尾):
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ digester-quick ---
[INFO] net.quzuo.demo:digester-quick:jar:1.0.0
[INFO] \- org.apache.commons:commons-digester3:jar:3.2:compile
[INFO] +- cglib:cglib:jar:2.2.2:compile
[INFO] | \- asm:asm:jar:3.3.1:compile
[INFO] +- commons-beanutils:commons-beanutils:jar:1.8.3:compile
[INFO] \- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
依赖齐备,环境就绪。
二、快速入门:把 XML 解析成对象列表
本示例演示 Digester 最核心的一句话:在解析到某个元素路径时创建对象、填属性、挂到父对象。写入 POJO src/main/java/net/quzuo/demo/Server.java:
package net.quzuo.demo;
public class Server {
private String name;
private String host;
private int port;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
@Override
public String toString() {
return "Server{name=" + name + ", host=" + host + ", port=" + port + "}";
}
}
写入输入文件 src/main/resources/servers.xml:
<?xml version="1.0" encoding="UTF-8"?>
<servers>
<server name="web01" host="10.0.0.11" port="8080"/>
<server name="web02" host="10.0.0.12" port="8080"/>
<server name="db01" host="10.0.0.21" port="3306"/>
</servers>
写入主程序 src/main/java/net/quzuo/demo/QuickStart.java:
package net.quzuo.demo;
import org.apache.commons.digester3.Digester;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class QuickStart {
public static void main(String[] args) throws Exception {
Digester d = new Digester();
d.addObjectCreate("servers", ArrayList.class);
d.addObjectCreate("servers/server", Server.class);
d.addSetProperties("servers/server");
d.addSetNext("servers/server", "add");
List<Server> servers = d.parse(new File("src/main/resources/servers.xml"));
System.out.println("解析到 " + servers.size() + " 台服务器:");
for (Server s : servers) {
System.out.println(" " + s);
}
System.out.println("commons-digester3 版本: "
+ Digester.class.getPackage().getImplementationVersion());
}
}
四行规则的含义(这是理解 Digester 的关键):
addObjectCreate("servers", ArrayList.class):解析到<servers>时,创建一个ArrayList并压入对象栈;addObjectCreate("servers/server", Server.class):解析到<servers/server>时,创建一个Server压栈;addSetProperties("servers/server"):把<server>上名为name/host/port的属性,按同名 setter 灌入栈顶的Server;addSetNext("servers/server", "add"):</server>结束时,调用父对象(ArrayList)的add方法,把当前Server收进去。
一个规则「进入元素时创建/设值、离开元素时挂载」的配对结构,就完成了 XML 到对象列表的映射。编译并把依赖类路径导出后运行:
mvn -q compile dependency:build-classpath -Dmdep.outputFile=cp.txt
java -Dfile.encoding=UTF-8 -cp "target/classes:$(cat cp.txt)" net.quzuo.demo.QuickStart
实测回显:
解析到 3 台服务器:
Server{name=web01, host=10.0.0.11, port=8080}
Server{name=web02, host=10.0.0.12, port=8080}
Server{name=db01, host=10.0.0.21, port=3306}
commons-digester3 版本: 3.2
三台服务器被正确解析。程序首次运行会打印 log4j:WARN No appenders could be found for logger (org.apache.commons.digester3.Digester.sax),这是 commons-logging 找不到日志实现时的提示,不影响功能。
💡 技术拓展:把 XML 映射成 Java 对象有多种做法,选型前要先分清各自的内存与用法代价:
| 方案 | 处理方式 | 内存占用 | 代码量 | 典型场景 |
|---|---|---|---|---|
| SAX | 事件流式推送,逐节点回调 | 低 | 高(手写回调与状态机) | 超大 XML、只读遍历 |
| DOM | 整棵树载入内存后操作 | 高 | 中 | 需随机访问、就地修改结构 |
| StAX | 拉模式流式,程序主动取事件 | 低 | 中 | 需精确控制读取流程 |
| JAXB | 注解绑定对象,需 XSD 生成类 | 中 | 低 | 标准 XML ↔ POJO 互转 |
| Digester | 规则式流式映射,边读边建对象 | 低 | 低 | 配置/规则文件转对象 |
上例就是 Digester 的典型形态:不用为 XML 定义固定 Schema,靠少量规则把结构宽松的配置映射成对象。
三、集成示例:属性与子元素混合的图书目录
真实业务里的 XML 往往既有元素属性、又有子元素。本示例用一份图书目录演示「属性 + 子元素」混合映射:id 走属性,title/author/price 走子元素。
写入 POJO src/main/java/net/quzuo/demo/Book.java:
package net.quzuo.demo;
public class Book {
private String id;
private String title;
private String author;
private double price;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
@Override
public String toString() {
return "Book{id=" + id + ", title=" + title + ", author=" + author + ", price=" + price + "}";
}
}
写入输入文件 src/main/resources/books.xml:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book id="b1">
<title>Apache Commons Digester 实战</title>
<author>quzuo</author>
<price>59.9</price>
</book>
<book id="b2">
<title>XML 解析与对象映射</title>
<author>tester</author>
<price>39.5</price>
</book>
</bookstore>
写入主程序 src/main/java/net/quzuo/demo/BookstoreDemo.java:
package net.quzuo.demo;
import org.apache.commons.digester3.Digester;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class BookstoreDemo {
public static void main(String[] args) throws Exception {
Digester d = new Digester();
d.setValidating(false);
d.addObjectCreate("bookstore", ArrayList.class);
d.addObjectCreate("bookstore/book", Book.class);
d.addSetProperties("bookstore/book");
d.addBeanPropertySetter("bookstore/book/title", "title");
d.addBeanPropertySetter("bookstore/book/author", "author");
d.addBeanPropertySetter("bookstore/book/price", "price");
d.addSetNext("bookstore/book", "add");
List<Book> books = d.parse(new File("src/main/resources/books.xml"));
System.out.println("解析到 " + books.size() + " 本图书:");
for (Book b : books) { System.out.println(" " + b); }
double sum = 0;
for (Book b : books) { sum += b.getPrice(); }
System.out.println("总价 = " + sum);
}
}
规则解读:
addObjectCreate("bookstore", ArrayList.class)与addObjectCreate("bookstore/book", Book.class)建立「集合 + 条目」两级对象;addSetProperties("bookstore/book")用元素属性给id赋值(属性名与 setter 同名即可);- 三条
addBeanPropertySetter("bookstore/book/title" 等)把对应子元素的元素体分别灌给title/author/price,这解决了「元素体文本」的映射; addSetNext("bookstore/book", "add")把每本Book收进列表。
编译并运行:
cd /opt/digester-quick
mvn -q compile
java -Dfile.encoding=UTF-8 -cp "target/classes:$(cat cp.txt)" net.quzuo.demo.BookstoreDemo
实测回显:
解析到 2 本图书:
Book{id=b1, title=Apache Commons Digester 实战, author=quzuo, price=59.9}
Book{id=b2, title=XML 解析与对象映射, author=tester, price=39.5}
总价 = 99.4
id 来自属性、书名/作者/价格来自子元素,两条 Book 记录都完整,price 被正确转成 double 并累加出 99.4。这套组合覆盖了绝大多数配置文件的结构形态。
四、规则声明方式对照与选型
同一套映射规则可以用三种方式声明,效果等价,按场景选择:
| 声明方式 | 写法入口 | 优点 | 适用场景 |
|---|---|---|---|
| Java 规则 API | Digester 的 addObjectCreate 等规则方法 | 规则与流程集中、可调试 | 规则少、逻辑复杂、需动态构造 |
| 注解式 | @ObjectCreate/@SetProperty/@SetNext 等标注在 POJO 上 | 对象即配置、改动集中 | POJO 结构稳定、规则随类走 |
| XML 规则文件 | digester-rules.xml + FromXmlRulesModule | 规则可外部化、运行时可替换 | 规则随业务方维护、需热替换 |
选型建议:入门与中小项目用 Java 规则 API(本文示例即是);POJO 与规则强绑定用注解式;规则需要交给非开发者维护或需运行时替换用 XML 规则文件。三种方式的代码与运行回显分别见《Apache Commons Digester 常用 CLI 命令与工具详解》篇,规则文件结构见《Apache Commons Digester 配置参数详解》篇。
五、集成注意事项
把 Digester 集成进既有工程时,下面几点最容易出问题,均已在实测中确认:
- 依赖必须包含传递依赖。3.2 的必需依赖是 cglib 2.2.2(含 asm 3.3.1)、commons-beanutils 1.8.3、commons-logging 1.1.1。Maven 用
commons-digester3:3.2一个坐标即可;手动部署时要么全部放入lib/,要么直接使用with-deps.jar,否则运行期报NoClassDefFoundError。 - XML 带命名空间时必须
setNamespaceAware(true),且规则的 pattern 只写 localName(不带前缀),否则匹配静默失效、解析到 0 条(详见配置参数篇)。 parse()的入参用File/InputStream/Reader对象,若直接传字符串会被当作 URL 解析并抛MalformedURLException: no protocol;本文示例统一传File对象。- 首次运行的 log4j 警告可忽略:
log4j:WARN No appenders could be found for logger (org.apache.commons.digester3.Digester.sax)是 commons-logging 1.1.1 找不到日志实现所致,不影响功能;要消除可引入 log4j 配置或改用 slf4j 桥接。 - 中文输出乱码是 locale 问题。实测本机
mvn -v显示platform encoding: ANSI_X3.4-1968,Java 程序System.out会退回 ASCII;解决方式是export LANG=C.UTF-8,或运行时追加-Dfile.encoding=UTF-8。
六、参考链接
- Apache Commons Digester 官方主页:https://commons.apache.org/proper/commons-digester/
- Digester 3.2 API 文档:https://commons.apache.org/proper/commons-digester/apidocs/index.html
- 官方快速上手与规则说明:https://commons.apache.org/proper/commons-digester/guide.html
- Maven 中央仓库 3.2 目录(含 pom 与 with-deps.jar):https://repo1.maven.org/maven2/org/apache/commons/commons-digester3/3.2/
- Apache Commons BeanUtils 文档(属性注入底层实现):https://commons.apache.org/proper/commons-beanutils/
- Maven 官方安装文档:https://maven.apache.org/install.html