Apache Commons Digester 快速入门与集成示例

以 Maven 工程演示 Apache Commons Digester 的快速上手与集成:从 XML 解析出对象列表,并给出属性与子元素混合映射的图书目录完整可跑示例。

Apache Commons DigesterApache Commons Digester2026/10/14

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 的关键):

  1. addObjectCreate("servers", ArrayList.class):解析到 <servers> 时,创建一个 ArrayList 并压入对象栈;
  2. addObjectCreate("servers/server", Server.class):解析到 <servers/server> 时,创建一个 Server 压栈;
  3. addSetProperties("servers/server"):把 <server> 上名为 name/host/port 的属性,按同名 setter 灌入栈顶的 Server;
  4. 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);
    }
}

规则解读:

  1. addObjectCreate("bookstore", ArrayList.class) 与 addObjectCreate("bookstore/book", Book.class) 建立「集合 + 条目」两级对象;
  2. addSetProperties("bookstore/book") 用元素属性给 id 赋值(属性名与 setter 同名即可);
  3. 三条 addBeanPropertySetter("bookstore/book/title" 等) 把对应子元素的元素体分别灌给 title/author/price,这解决了「元素体文本」的映射;
  4. 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 规则 APIDigester 的 addObjectCreate 等规则方法规则与流程集中、可调试规则少、逻辑复杂、需动态构造
注解式@ObjectCreate/@SetProperty/@SetNext 等标注在 POJO 上对象即配置、改动集中POJO 结构稳定、规则随类走
XML 规则文件digester-rules.xml + FromXmlRulesModule规则可外部化、运行时可替换规则随业务方维护、需热替换

选型建议:入门与中小项目用 Java 规则 API(本文示例即是);POJO 与规则强绑定用注解式;规则需要交给非开发者维护或需运行时替换用 XML 规则文件。三种方式的代码与运行回显分别见《Apache Commons Digester 常用 CLI 命令与工具详解》篇,规则文件结构见《Apache Commons Digester 配置参数详解》篇。

五、集成注意事项

把 Digester 集成进既有工程时,下面几点最容易出问题,均已在实测中确认:

  1. 依赖必须包含传递依赖。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。
  2. XML 带命名空间时必须 setNamespaceAware(true),且规则的 pattern 只写 localName(不带前缀),否则匹配静默失效、解析到 0 条(详见配置参数篇)。
  3. parse() 的入参用 File/InputStream/Reader 对象,若直接传字符串会被当作 URL 解析并抛 MalformedURLException: no protocol;本文示例统一传 File 对象。
  4. 首次运行的 log4j 警告可忽略:log4j:WARN No appenders could be found for logger (org.apache.commons.digester3.Digester.sax) 是 commons-logging 1.1.1 找不到日志实现所致,不影响功能;要消除可引入 log4j 配置或改用 slf4j 桥接。
  5. 中文输出乱码是 locale 问题。实测本机 mvn -v 显示 platform encoding: ANSI_X3.4-1968,Java 程序 System.out 会退回 ASCII;解决方式是 export LANG=C.UTF-8,或运行时追加 -Dfile.encoding=UTF-8。

六、参考链接

  1. Apache Commons Digester 官方主页:https://commons.apache.org/proper/commons-digester/
  2. Digester 3.2 API 文档:https://commons.apache.org/proper/commons-digester/apidocs/index.html
  3. 官方快速上手与规则说明:https://commons.apache.org/proper/commons-digester/guide.html
  4. Maven 中央仓库 3.2 目录(含 pom 与 with-deps.jar):https://repo1.maven.org/maven2/org/apache/commons/commons-digester3/3.2/
  5. Apache Commons BeanUtils 文档(属性注入底层实现):https://commons.apache.org/proper/commons-beanutils/
  6. Maven 官方安装文档:https://maven.apache.org/install.html