Apache Commons Digester 常用 CLI 命令与工具详解
讲清 Apache Commons Digester 没有独立命令行工具的原因,用 java -cp 自建 XML 解析命令行工具,并详解规则 API 与注解式 API 的用法与真实回显。
Apache Commons Digester 常用 CLI 命令与工具详解
目标:说明 Apache Commons Digester 为什么没有独立命令行工具,并给出一个用
java -cp直接运行的自建解析工具,同时详解规则 API 与注解式 API 的用法。全部命令与回显已在 Ubuntu 22.04(JDK 17)实测通过。
一、环境信息
本章先准备一个不依赖构建工具的最小运行环境:下载 3.2 的胖包(with-deps.jar 已把传递依赖打进同一个 jar),再写入示例源码,后续所有命令都基于它。
创建目录并下载:
mkdir -p /opt/digester-cli
cd /opt/digester-cli
curl -fsSLO https://repo1.maven.org/maven2/org/apache/commons/commons-digester3/3.2/commons-digester3-3.2-with-deps.jar
ls -l
实测回显:
-rw-r--r-- 1 root root 734212 Oct 13 16:57 commons-digester3-3.2-with-deps.jar
写入被映射的 POJO Server.java:
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 + "}";
}
}
写入输入文件 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>
💡 技术拓展:把 XML 映射成 Java 对象有多种做法,选型前要先分清各自的内存与用法代价:
| 方案 | 处理方式 | 内存占用 | 代码量 | 典型场景 |
|---|---|---|---|---|
| SAX | 事件流式推送,逐节点回调 | 低 | 高(手写回调与状态机) | 超大 XML、只读遍历 |
| DOM | 整棵树载入内存后操作 | 高 | 中 | 需随机访问、就地修改结构 |
| StAX | 拉模式流式,程序主动取事件 | 低 | 中 | 需精确控制读取流程 |
| JAXB | 注解绑定对象,需 XSD 生成类 | 中 | 低 | 标准 XML ↔ POJO 互转 |
| Digester | 规则式流式映射,边读边建对象 | 低 | 低 | 配置/规则文件转对象 |
本文全部示例使用 Apache Commons Digester;它以「规则」描述「解析到哪个路径时做什么」,规则可以用 Java API、注解或 XML 文件三种方式声明,分别在第四、五、六章展开。
二、关于「命令行」:Digester 是库而非 CLI 程序
与 curl、openssl 这类自带命令行的工具不同,Apache Commons Digester 是一个 Java 库,不提供形如 $ digester 这样的可执行程序。实测也印证了这一点:直接尝试运行纯库 jar,会因为没有主类而失败:
curl -fsSL -o commons-digester3-3.2.jar https://repo1.maven.org/maven2/org/apache/commons/commons-digester3/3.2/commons-digester3-3.2.jar
java -jar commons-digester3-3.2.jar
实测回显:
no main manifest attribute, in commons-digester3-3.2.jar
因此,所谓「用 Digester 做命令行」实际是:自己写一个带 main 方法的类,用 Digester 解析命令行传入的 XML,再把结果打印出来,最后用 java -cp 运行它。下一章给出这样一个可直接使用的工具。
三、自建命令行解析工具 XmlModelTool
写入 XmlModelTool.java——一个把 XML 文件路径作为参数、解析后逐条打印的迷你工具:
import org.apache.commons.digester3.Digester;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class XmlModelTool {
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.err.println("用法: XmlModelTool <xml文件>");
System.exit(2);
}
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(args[0]));
System.out.println("文件: " + args[0]);
System.out.println("记录数: " + servers.size());
for (Server s : servers) { System.out.println(s); }
}
}
要点:args 为空时打印用法并以退出码 2 结束(便于脚本判断);parse(new File(args[0])) 用 File 对象而非字符串,避免把路径当 URL 解析。
编译并运行:
cd /opt/digester-cli
mkdir -p out
javac -encoding UTF-8 -cp commons-digester3-3.2-with-deps.jar -d out Server.java XmlModelTool.java
java -Dfile.encoding=UTF-8 -cp "commons-digester3-3.2-with-deps.jar:out" XmlModelTool servers.xml
实测回显:
文件: servers.xml
记录数: 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}
不带参数运行,验证用法提示与退出码:
java -Dfile.encoding=UTF-8 -cp "commons-digester3-3.2-with-deps.jar:out" XmlModelTool
echo "exit=$?"
实测回显:
用法: XmlModelTool <xml文件>
exit=2
这个工具就是「Digester 的命令行形态」:java -cp <类路径> XmlModelTool <xml文件>。只要替换 Server 与规则,就能复用成解析任意结构 XML 的 CLI。
四、规则 API 方法参考
Digester 的规则 API 都挂在 Digester 实例上,方法名一律以 add 开头、第一个参数是元素路径 pattern(用 / 连接,如 servers/server)。写入 RuleApiDemo.java 逐方法演示:
import org.apache.commons.digester3.Digester;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
public class RuleApiDemo {
public static void main(String[] args) throws Exception {
Digester d1 = new Digester();
d1.addObjectCreate("servers", ArrayList.class);
d1.addObjectCreate("servers/server", Server.class);
d1.addSetProperties("servers/server");
d1.addSetNext("servers/server", "add");
List<Server> r1 = d1.parse(new StringReader(
"<servers><server name=\"a\" host=\"h1\" port=\"1\"/></servers>"));
System.out.println("[addObjectCreate/addSetProperties/addSetNext] " + r1);
Digester d2 = new Digester();
d2.addObjectCreate("servers", ArrayList.class);
d2.addObjectCreate("servers/server", Server.class);
d2.addSetProperties("servers/server");
d2.addBeanPropertySetter("servers/server/host");
d2.addSetNext("servers/server", "add");
List<Server> r2 = d2.parse(new StringReader(
"<servers><server name=\"b\" host=\"attrHost\" port=\"2\"><host>bodyHost</host></server></servers>"));
System.out.println("[addBeanPropertySetter] host=" + r2.get(0).getHost());
Digester d3 = new Digester();
d3.addObjectCreate("servers", ArrayList.class);
d3.addObjectCreate("servers/server", Server.class);
d3.addCallMethod("servers/server/name", "setName", 0);
d3.addSetNext("servers/server", "add");
List<Server> r3 = d3.parse(new StringReader(
"<servers><server><name>fromBodyCall</name></server></servers>"));
System.out.println("[addCallMethod] name=" + r3.get(0).getName());
Digester d4 = new Digester();
d4.addObjectCreate("servers", ArrayList.class);
d4.addObjectCreate("servers/server", Server.class);
d4.addSetProperty("servers/server/property", "name", "value");
d4.addSetNext("servers/server", "add");
List<Server> r4 = d4.parse(new StringReader(
"<servers><server><property name=\"host\" value=\"forced.example.com\"/></server></servers>"));
System.out.println("[addSetProperty] host=" + r4.get(0).getHost());
}
}
编译运行:
cd /opt/digester-cli
javac -encoding UTF-8 -cp commons-digester3-3.2-with-deps.jar -d out Server.java RuleApiDemo.java
java -Dfile.encoding=UTF-8 -cp "commons-digester3-3.2-with-deps.jar:out" RuleApiDemo
实测回显:
[addObjectCreate/addSetProperties/addSetNext] [Server{name=a, host=h1, port=1}]
[addBeanPropertySetter] host=bodyHost
[addCallMethod] name=fromBodyCall
[addSetProperty] host=forced.example.com
四行输出对应四组规则的实测结果,逐条解读:
- 第一行:
addObjectCreate(pattern, Class)创建对象、addSetProperties(pattern)按属性名灌值、addSetNext(pattern, "add")挂到父列表,三者组合即「XML 属性 → 对象列表」的常规链路。 - 第二行:
addBeanPropertySetter("servers/server/host")用子元素<host>bodyHost</host>的元素体设置Server.host属性;它覆盖了属性方式传入的attrHost,结果为bodyHost。 - 第三行:
addCallMethod("servers/server/name", "setName", 0)调用setName。第三个参数paramCount=0的含义是「用元素体自动转成 1 个 String 参数」,因此把<name>fromBodyCall</name>的元素体传了进去。 - 第四行:
addSetProperty("servers/server/property", "name", "value")用子元素property的name/value属性对,指定「属性名 → 值」直接注入,把host设成了forced.example.com。
常用规则 API 对照:
| 规则 API | 触发时机 | 典型用途 |
|---|---|---|
addObjectCreate(pattern, Class) | 进入元素时 | 创建对象并压栈 |
addSetProperties(pattern) | 进入元素时 | 按元素属性名匹配同名 setter |
addSetProperty(pattern, attrName, attrValue) | 进入元素时 | 按指定属性直接设值 |
addBeanPropertySetter(pattern) | 进入元素时 | 用元素体设置同名 Bean 属性 |
addCallMethod(pattern, method, paramCount) | 进入元素时 | 调方法,元素体作参数 |
addCallParam(pattern, paramIndex) | 进入元素时 | 为方法调用提供指定参数(常配 addCallMethod) |
addSetNext(pattern, method) | 离开元素时 | 把对象挂到父对象(如 add) |
addSetTop / addSetRoot | 进入/离开时 | 压入栈顶 / 弹到根 |
addFactoryCreate(pattern, factory) | 进入元素时 | 用自定义工厂创建对象 |
其中 paramCount=0 表示「元素体作 1 个 String 参数」是最易踩的语义坑;若要 0 个参数真正无参调用,应使用 addCallMethod(pattern, method) 的两参重载。
五、注解式 API
除 Java 规则 API 与 XML 规则文件外,3.x 还支持注解式声明:把规则直接标在 POJO 类/字段/方法上,用 FromAnnotationsRuleModule 扫描绑定。它让「对象结构」与「映射规则」集中在一处,维护更直观。
写入带注解的 POJO AnnotatedServer.java:
import org.apache.commons.digester3.annotations.rules.ObjectCreate;
import org.apache.commons.digester3.annotations.rules.SetProperty;
@ObjectCreate(pattern = "servers/server")
public class AnnotatedServer {
@SetProperty(pattern = "servers/server", attributeName = "name")
private String name;
@SetProperty(pattern = "servers/server", attributeName = "host")
private String host;
@SetProperty(pattern = "servers/server", attributeName = "port")
private Integer 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 Integer getPort() { return port; }
public void setPort(Integer port) { this.port = port; }
@Override
public String toString() {
return "AnnotatedServer{name=" + name + ", host=" + host + ", port=" + port + "}";
}
}
写入承载列表的汇集类 ServerCatalog.java(用 @SetNext 把子对象收集进列表):
import org.apache.commons.digester3.annotations.rules.ObjectCreate;
import org.apache.commons.digester3.annotations.rules.SetNext;
import java.util.ArrayList;
import java.util.List;
@ObjectCreate(pattern = "servers")
public class ServerCatalog {
private final List<AnnotatedServer> servers = new ArrayList<>();
@SetNext
public void addServer(AnnotatedServer server) {
servers.add(server);
}
public List<AnnotatedServer> getServers() { return servers; }
}
写入加载程序 AnnotationDemo.java(FromAnnotationsRuleModule.configureRules() 里绑定要扫描的类):
import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.annotations.FromAnnotationsRuleModule;
import org.apache.commons.digester3.binder.DigesterLoader;
import java.io.File;
public class AnnotationDemo {
public static void main(String[] args) throws Exception {
DigesterLoader loader = DigesterLoader.newLoader(new FromAnnotationsRuleModule() {
@Override
protected void configureRules() {
bindRulesFrom(ServerCatalog.class);
bindRulesFrom(AnnotatedServer.class);
}
});
Digester d = loader.newDigester();
ServerCatalog catalog = d.parse(new File("servers.xml"));
System.out.println("注解式解析到 " + catalog.getServers().size() + " 台:");
for (AnnotatedServer s : catalog.getServers()) { System.out.println(" " + s); }
}
}
编译运行:
cd /opt/digester-cli
javac -encoding UTF-8 -cp commons-digester3-3.2-with-deps.jar -d out Server.java AnnotatedServer.java ServerCatalog.java AnnotationDemo.java
java -Dfile.encoding=UTF-8 -cp "commons-digester3-3.2-with-deps.jar:out" AnnotationDemo
实测回显:
注解式解析到 3 台:
AnnotatedServer{name=web01, host=10.0.0.11, port=8080}
AnnotatedServer{name=web02, host=10.0.0.12, port=8080}
AnnotatedServer{name=db01, host=10.0.0.21, port=3306}
注解与规则的对应关系:
| 注解 | 等价规则 API | 标注位置 | 作用 |
|---|---|---|---|
@ObjectCreate | addObjectCreate | 类 | 指定 pattern 上创建该类的对象 |
@SetProperty | addSetProperty | 字段 | 用元素属性设值(须给 attributeName) |
@BeanPropertySetter | addBeanPropertySetter | 字段/方法 | 用元素体设值 |
@CallMethod | addCallMethod | 方法 | 解析到 pattern 时调用该方法 |
@SetNext | addSetNext | 方法 | 把父/子对象关联起来(本示例收集子对象) |
@SetRoot / @SetTop | addSetRoot / addSetTop | 字段/方法 | 关联根/栈顶对象 |
需要特别说明:Digester 3.2 的注解集合里没有 @SetProperties(对应整组属性注入的那个注解)。需要「按属性名批量注入」时,要么用 Java 规则 API 的 addSetProperties,要么在 POJO 上逐个字段标 @SetProperty。此外 @SetNext 也对应 XML 规则里的 set-next-rule,本示例用它把每个 AnnotatedServer 收进 ServerCatalog 的列表。
六、XML 规则文件加载(声明规则的第三种方式)
规则也可以写在 XML 文件里,用 FromXmlRulesModule 加载(详见《Apache Commons Digester 配置参数详解》第六章的规则文件结构)。最小用法如下:写入 digester-rules.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE digester-rules PUBLIC "-//Apache Commons //DTD digester-rules XML V1.0//EN"
"http://commons.apache.org/digester/dtds/digester-rules-3.0.dtd">
<digester-rules>
<pattern value="servers">
<object-create-rule classname="java.util.ArrayList"/>
</pattern>
<pattern value="servers/server">
<object-create-rule classname="Server"/>
<set-properties-rule/>
<set-next-rule methodname="add"/>
</pattern>
</digester-rules>
写入 XmlRulesDemo.java:
import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.apache.commons.digester3.xmlrules.FromXmlRulesModule;
import java.io.File;
import java.util.List;
public class XmlRulesDemo {
public static void main(String[] args) throws Exception {
DigesterLoader loader = DigesterLoader.newLoader(new FromXmlRulesModule() {
@Override
protected void loadRules() {
loadXMLRules(new File("digester-rules.xml"));
}
});
Digester d = loader.newDigester();
List<Server> servers = d.parse(new File("servers.xml"));
System.out.println("XML 规则文件加载成功,解析到 " + servers.size() + " 台:");
for (Server s : servers) { System.out.println(" " + s); }
}
}
编译运行:
cd /opt/digester-cli
javac -encoding UTF-8 -cp commons-digester3-3.2-with-deps.jar -d out Server.java XmlRulesDemo.java
java -Dfile.encoding=UTF-8 -cp "commons-digester3-3.2-with-deps.jar:out" XmlRulesDemo
实测回显:
XML 规则文件加载成功,解析到 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}
三种声明方式(Java API / 注解 / XML 规则文件)产出的对象完全一致,可按团队习惯选择:代码内聚用 Java API,对象即配置用注解,规则需外部化(可运行时替换)用 XML 文件。注意 classname="Server" 用的是类全名,示例因 Server 在默认包而直接写 Server;若放在 net.quzuo.demo 包下则应写 net.quzuo.demo.Server。
七、参考链接
- Apache Commons Digester 官方主页:https://commons.apache.org/proper/commons-digester/
- Digester 3.2 API 文档(规则 API 与注解清单):https://commons.apache.org/proper/commons-digester/apidocs/index.html
- 注解式 API 所在包
org.apache.commons.digester3.annotations:https://commons.apache.org/proper/commons-digester/apidocs/org/apache/commons/digester3/annotations/package-summary.html - XML 规则文件 DTD:https://commons.apache.org/proper/commons-digester/dtds/digester-rules-3.0.dtd
- Maven 中央仓库 3.2 目录(含 with-deps.jar):https://repo1.maven.org/maven2/org/apache/commons/commons-digester3/3.2/
- SAX 规范(Digester 底层解析基础):https://docs.oracle.com/en/java/javase/17/docs/api/java.xml/org/xml/sax/package-summary.html