Apache Commons Digester 配置参数详解

详解 Apache Commons Digester 的核心配置参数:命名空间感知、DTD 校验、SAX 特性与属性、错误处理、类加载器、规则注册顺序以及 digester-rules.xml 规则文件结构。

Apache Commons DigesterApache Commons Digester2026/10/14

Apache Commons Digester 配置参数详解

目标:逐一讲清 Apache Commons Digester 的配置参数与规则注册语义,并给出每一项的真实现象与回显。全部命令与回显已在 Ubuntu 22.04(JDK 17)实测通过。

一、环境信息

本章先准备一个可直接运行的最小环境,后续每一节的示例都基于它。Commons Digester 运行时不必依赖构建工具,把「主 jar + 传递依赖」这一个胖包下载下来,即可用 java -cp 直接编译运行。

先创建目录并下载 3.2 的胖包(with-deps.jar 已把 cglib、commons-beanutils、commons-logging 打进同一个 jar):

mkdir -p /opt/digester-cfg
cd /opt/digester-cfg
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 + "}";
    }
}

写入配置文件 gservers.xml(用于规则顺序与元素体传参的示例):

<?xml version="1.0" encoding="UTF-8"?>
<servers>
  <server name="web01" host="ATTR">BODYHOST</server>
</servers>

写入本章的综合演示程序 ConfigDemo.java(把默认值与各配置项现象一次性打印出来,后面每章引用其中一段):

import org.apache.commons.digester3.Digester;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
import java.io.File;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

public class ConfigDemo {

    static Digester serverDigester() {
        Digester d = new Digester();
        d.addObjectCreate("servers", ArrayList.class);
        d.addObjectCreate("servers/server", Server.class);
        d.addSetProperties("servers/server");
        d.addSetNext("servers/server", "add");
        return d;
    }

    static final String PLAIN =
        "<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>";

    static final String NS =
        "<ns:servers xmlns:ns=\"urn:demo\">"
        + "<ns:server name=\"web01\" host=\"10.0.0.11\" port=\"8080\"/>"
        + "<ns:server name=\"web02\" host=\"10.0.0.12\" port=\"8080\"/>"
        + "</ns:servers>";

    static final String DTD =
        "<!DOCTYPE servers ["
        + "<!ELEMENT servers (server*)>"
        + "<!ELEMENT server EMPTY>"
        + "<!ATTLIST server name CDATA #IMPLIED host CDATA #IMPLIED port CDATA #IMPLIED>"
        + "]>"
        + PLAIN;

    public static void main(String[] args) throws Exception {
        Digester d0 = new Digester();
        System.out.println("默认 namespaceAware = " + d0.getNamespaceAware());
        System.out.println("默认 validating     = " + d0.getValidating());
        System.out.println("默认 classLoader    = " + d0.getClassLoader());
        System.out.println("default errorHandler= " + d0.getErrorHandler());
        System.out.println("feature(disallow-doctype-decl) = "
            + d0.getFeature("http://apache.org/xml/features/disallow-doctype-decl"));

        System.out.println("--- A. setNamespaceAware(false) + 普通 XML ---");
        Digester da = serverDigester();
        da.setNamespaceAware(false);
        List<Server> ra = da.parse(new StringReader(PLAIN));
        System.out.println("解析到 " + ra.size() + " 台");

        System.out.println("--- B. setNamespaceAware(true) + 命名空间 XML ---");
        Digester db = new Digester();
        db.setNamespaceAware(true);
        db.addObjectCreate("servers", ArrayList.class);
        db.addObjectCreate("servers/server", Server.class);
        db.addSetProperties("servers/server");
        db.addSetNext("servers/server", "add");
        List<Server> rb = db.parse(new StringReader(NS));
        System.out.println("命名空间模式解析到 " + rb.size() + " 台: " + rb);

        System.out.println("--- C. 默认(未开命名空间) + 命名空间 XML ---");
        Digester dc = serverDigester();
        dc.setNamespaceAware(false);
        List<Server> rc = dc.parse(new StringReader(NS));
        System.out.println("未开命名空间解析到 " + (rc == null ? 0 : rc.size()) + " 台");

        System.out.println("--- D. setValidating(true) + 内嵌 DTD ---");
        Digester dd = serverDigester();
        dd.setValidating(true);
        dd.setErrorHandler(new CapturingHandler());
        List<Server> rd = dd.parse(new StringReader(DTD));
        System.out.println("DTD 校验通过,解析到 " + rd.size() + " 台");

        System.out.println("--- E. setFeature(disallow-doctype-decl,true) ---");
        Digester de = serverDigester();
        de.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        try {
            de.parse(new StringReader(DTD));
            System.out.println("未按预期拒绝");
        } catch (Exception ex) {
            System.out.println("已拒绝 DOCTYPE: " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
        }

        System.out.println("--- F. setErrorHandler 捕获格式错误 ---");
        Digester df = serverDigester();
        CapturingHandler h = new CapturingHandler();
        df.setErrorHandler(h);
        try {
            df.parse(new StringReader("<servers><server name=\"x\"></servers>"));
        } catch (Exception ex) {
            System.out.println("抛出: " + ex.getClass().getSimpleName());
        }
        System.out.println("errorHandler 记录条数: " + h.messages.size());
        if (!h.messages.isEmpty()) {
            System.out.println("首条: " + h.messages.get(0));
        }

        System.out.println("--- G. 规则注册顺序(先 SetProperties 设 host,后 setHost 用元素体覆盖) ---");
        Digester dg = new Digester();
        dg.addObjectCreate("servers", ArrayList.class);
        dg.addObjectCreate("servers/server", Server.class);
        dg.addSetProperties("servers/server");
        dg.addCallMethod("servers/server", "setHost", 0);
        dg.addSetNext("servers/server", "add");
        List<Server> rg = dg.parse(new File("gservers.xml"));
        System.out.println("首台 host = " + rg.get(0).getHost());
    }

    static class CapturingHandler implements ErrorHandler {
        final List<String> messages = new ArrayList<>();
        public void warning(SAXParseException e) { messages.add("WARN " + e.getMessage()); }
        public void error(SAXParseException e) { messages.add("ERROR " + e.getMessage()); }
        public void fatalError(SAXParseException e) { messages.add("FATAL " + e.getMessage()); }
    }
}

编译并运行:

cd /opt/digester-cfg
mkdir -p out
javac -encoding UTF-8 -cp commons-digester3-3.2-with-deps.jar -d out Server.java ConfigDemo.java
java -Dfile.encoding=UTF-8 -cp "commons-digester3-3.2-with-deps.jar:out" ConfigDemo

实测回显:

默认 namespaceAware = false
默认 validating     = false
默认 classLoader    = jdk.internal.loader.ClassLoaders$AppClassLoader@30946e09
default errorHandler= null
feature(disallow-doctype-decl) = false
--- A. setNamespaceAware(false) + 普通 XML ---
解析到 3 台
--- B. setNamespaceAware(true) + 命名空间 XML ---
命名空间模式解析到 2 台: [Server{name=web01, host=10.0.0.11, port=8080}, Server{name=web02, host=10.0.0.12, port=8080}]
--- C. 默认(未开命名空间) + 命名空间 XML ---
未开命名空间解析到 0 台
--- D. setValidating(true) + 内嵌 DTD ---
DTD 校验通过,解析到 3 台
--- E. setFeature(disallow-doctype-decl,true) ---
已拒绝 DOCTYPE: SAXParseException: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
--- F. setErrorHandler 捕获格式错误 ---
抛出: SAXParseException
errorHandler 记录条数: 1
首条: FATAL The end-tag for element type "server" must end with a '>' delimiter.
--- G. 规则注册顺序(先 SetProperties 设 host,后 setHost 用元素体覆盖) ---
首台 host = BODYHOST

说明:上表为程序的标准输出(stdout)。E、F 两段让解析抛错时,Digester 会把默认的 fatalError 日志堆栈写到 stderr(以 SEVERE 开头),属正常现象,不影响 stdout 结果。程序首次运行还会打印 log4j:WARN No appenders could be found 的提示,同样可忽略。

💡 技术拓展:把 XML 映射成 Java 对象有多种做法,配置方式与内存代价各不相同:

方案处理方式内存占用代码量典型场景
SAX事件流式推送,逐节点回调低高(手写回调与状态机)超大 XML、只读遍历
DOM整棵树载入内存后操作高中需随机访问、就地修改结构
StAX拉模式流式,程序主动取事件低中需精确控制读取流程
JAXB注解绑定对象,需 XSD 生成类中低标准 XML ↔ POJO 互转
Digester规则式流式映射,边读边建对象低低配置/规则文件转对象

本文全部示例使用 Apache Commons Digester 的「规则式流式映射」;它底层仍是 SAX 解析器,因此第三、四、五章讲到的参数,本质是对底层 SAX/XMLReader 的配置。

二、配置参数总览

Digester 把配置分成三组:一组开关解析模式(是否命名空间感知、是否校验),一组透传给底层 SAX 解析器(feature 与 property),还有一组是 Digester 自身的行为(错误处理、类加载器)。下面是本文覆盖的核心 setter/getter 与实测默认值(默认值取自上一章程序输出):

配置项Setter / Getter实测默认值作用
命名空间感知setNamespaceAware / getNamespaceAwarefalse是否按命名空间解析,开启后规则 pattern 用 localName 匹配
文档校验setValidating / getValidatingfalse是否按 DTD/XSD 校验文档结构
解析器特性setFeature / getFeature按解析器默认(示例 disallow-doctype-decl 为 false)透传 SAX 解析器 feature,如禁用 DOCTYPE
解析器属性setProperty / getProperty按解析器默认透传 SAX 解析器 property,如外部实体访问策略
错误处理器setErrorHandler / getErrorHandlernull自定义 warning/error/fatalError 回调
类加载器setClassLoader / getClassLoader应用类加载器(AppClassLoader)规则创建对象时用于解析类名
XIncludesetXIncludeAware / getXIncludeAwarefalse是否解析 XML Inclusions
实体解析器setEntityResolver / getEntityResolvernull自定义外部实体定位
公共标识setPublicId / getPublicIdnull设置 DTD 的 publicId

要点:所有 setter 都应在第一次 parse() 之前调用,配置只在构造 XMLReader 时生效;已开始解析后改这些参数不会改变行为。

三、命名空间感知:setNamespaceAware

本节对应上一章输出中的 A、B、C 三段。默认 false,此时规则 pattern 用元素的 qName(含前缀)匹配;true 时用 localName(剥离前缀)匹配。

A 段(setNamespaceAware(false) + 普通无命名空间 XML)——pattern servers/servers/server 与 qName 一致,正常解析:

--- A. setNamespaceAware(false) + 普通 XML ---
解析到 3 台

B 段(setNamespaceAware(true) + 带命名空间 XML)——XML 里是 <ns:servers xmlns:ns="urn:demo">,但规则仍写 servers/servers/server,因为命名空间感知下匹配用的是被剥离前缀后的 localName:

--- B. setNamespaceAware(true) + 命名空间 XML ---
命名空间模式解析到 2 台: [Server{name=web01, host=10.0.0.11, port=8080}, Server{name=web02, host=10.0.0.12, port=8080}]

注意这里解析到的是 2 台(该段 XML 只写了 web01、web02 两条),且能正确生成对象。

C 段(默认 setNamespaceAware(false) + 同一份命名空间 XML)——此时匹配用 qName ns:servers,与规则里的 servers 不相等,因此一条都匹配不到:

--- C. 默认(未开命名空间) + 命名空间 XML ---
未开命名空间解析到 0 台

结论:XML 带命名空间时,必须 setNamespaceAware(true),并让 pattern 只写 localName(不要带前缀);否则规则静默失配,parse() 返回空(示例中 null,代码里用 rc == null ? 0 : rc.size() 做了空值保护)。这条差异是 Digester 使用中最常见的「解析不到对象」根因。

四、文档校验:setValidating

本节对应输出中的 D 段。默认 false,即不按 DTD/XSD 校验文档,仅做基本的格式良好(well-formed)检查;setValidating(true) 后,解析器会要求文档带 DTD(或 Schema)并对结构、属性做校验。

D 段用一份内嵌 DTD 的 XML(在 <!DOCTYPE servers 后以内嵌子集定义 server 必须满足的属性)演示开启校验后仍能正常解析:

--- D. setValidating(true) + 内嵌 DTD ---
DTD 校验通过,解析到 3 台

要点与注意:

  1. setValidating(true) 时必须设置错误处理器(见第六章),否则校验错误只会走默认日志、不易定位。
  2. 校验失败会以 SAXParseException 抛出,可利用它做「配置合法性」的前置检查。
  3. 校验是 Digester 透传给底层 SAX 解析器的能力;它不改规则行为,只增加结构约束。若 XML 没有 DTD/Schema,setValidating(true) 会因找不到校验依据而报错,此时应保持默认 false。

五、解析器特性与属性:setFeature / setProperty

本节对应输出中的 E 段。setFeature(name, value) 与 setProperty(name, value) 把配置透传给底层 SAX 解析器(其底层是 XMLReader)。

E 段演示一个安全加固场景:设置 disallow-doctype-decl 特性为 true,让解析器在遇到 DOCTYPE 时直接拒绝,从而防御 XXE(XML External Entity)注入:

--- E. setFeature(disallow-doctype-decl,true) ---
已拒绝 DOCTYPE: SAXParseException: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.

实测确认该特性默认值为 false(见第二章总览输出),需显式开启。常用特性与属性对照如下:

名称类型实测默认推荐值作用
http://apache.org/xml/features/disallow-doctype-declfeaturefalsetrue(不需 DTD 时)禁止 DOCTYPE,阻断 XXE
http://xml.org/sax/features/external-general-entitiesfeature多为 truefalse禁止外部通用实体
http://xml.org/sax/features/external-parameter-entitiesfeature多为 truefalse禁止外部参数实体
http://apache.org/xml/features/nonvalidating/load-external-dtdfeature多为 truefalse不加载外部 DTD
http://javax.xml.XMLConstants/property/accessExternalDTDproperty依实现""禁止访问外部 DTD
http://javax.xml.XMLConstants/property/accessExternalSchemaproperty依实现""禁止访问外部 Schema

用法与 setFeature 相同,属性用 setProperty("http://javax.xml.XMLConstants/property/accessExternalDTD", "") 这类调用。注意:这些名字是否被接受取决于底层解析器(JDK 自带的 Xerces 支持上面这些),不支持的解析器会抛 SAXNotRecognizedException。

⚠️ 安全提示:默认解析器允许加载外部实体,仅在可信输入源下使用默认配置;面向不可信输入时应按上表开启 disallow-doctype-decl 等加固项。

六、错误处理:setErrorHandler

本节对应输出中的 F 段。getErrorHandler() 实测默认返回 null,此时 Digester 用内部的默认处理器(把错误经 commons-logging 记日志)。要拿到结构化的错误信息,需实现 org.xml.sax.ErrorHandler 并调用 setErrorHandler。

F 段用一份格式错误的 XML(<server name="x"></servers> 开始/结束标签不匹配)演示捕获过程:

--- F. setErrorHandler 捕获格式错误 ---
抛出: SAXParseException
errorHandler 记录条数: 1
首条: FATAL The end-tag for element type "server" must end with a '>' delimiter.

解读:parse() 最终仍以 SAXParseException 抛出(说明错误已发生),但自定义处理器在抛出前被回调一次,把它归为 fatalError,因此记录条数为 1、首条以 FATAL 开头。ErrorHandler 接口有三个方法:

void warning(SAXParseException e) throws SAXException;      // 非致命告警
void error(SAXParseException e) throws SAXException;        // 可恢复错误(校验失败常走这里)
void fatalError(SAXParseException e) throws SAXException;   // 不可恢复错误(格式错误走这里)

实践中常配合 setValidating(true) 一起用:校验错误走 error,格式错误走 fatalError,可在处理器里分类收集,形成可读的校验报告。

七、类加载器:setClassLoader

本节对应输出中的第一组默认值。getClassLoader() 实测默认返回应用类加载器(示例输出 jdk.internal.loader.ClassLoaders$AppClassLoader@30946e09)。Digester 在按规则创建对象、解析属性类型时会用它去 Class.forName,因此当目标类不在应用类路径、而由自定义加载器提供时,需要显式设置。

典型用法(把规则文件所在上下文的类加载器交给 Digester):

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.apache.commons.digester3.xmlrules.FromXmlRulesModule;
import java.io.File;

public class CustomLoaderDemo {
    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();
        d.setClassLoader(Thread.currentThread().getContextClassLoader());
        System.out.println("已设置类加载器: " + d.getClassLoader());
    }
}

要点:setClassLoader 只影响 Digester 解析类名的来源,不改规则匹配;在聚合了多个业务插件(各插件类由独立加载器提供)的场景里,把它设为能同时看到这些类的加载器即可。若目标类都在应用类路径内,保持默认即可,无需设置。

八、规则注册与执行顺序

本节对应输出中的 G 段。Digester 的核心是「规则(Rule)」:每个 pattern 上注册一组规则,进入元素时按注册顺序依次触发。因此同一属性被多条规则写入时,后注册的规则生效。

G 段在同一 pattern servers/server 上先注册 addSetProperties(把属性 host="ATTR" 灌入 host 字段),随后注册 addCallMethod("servers/server", "setHost", 0)(用元素体 BODYHOST 调 setHost)。由于后者注册更晚,它覆盖了前者:

--- G. 规则注册顺序(先 SetProperties 设 host,后 setHost 用元素体覆盖) ---
首台 host = BODYHOST

注意 addCallMethod(pattern, methodName, paramCount) 的第三个参数:传 0 表示「用元素体自动转成 1 个 String 参数调用」(而非「0 个参数调用」),这一点极易误解——所以此行才把 <server> 的元素体文本 BODYHOST 传进了 setHost。规则顺序的三条实践结论:

  1. 同一 pattern 的多条规则按注册先后触发,写同一目标时后者胜出;
  2. addObjectCreate 要最先注册(先有对象,后续 set/call 才有目标);
  3. addSetNext 通常在末尾注册(对象填充完成后再挂到父对象)。

九、XML 规则文件 digester-rules.xml

把规则从 Java 代码搬到 XML 文件,可让规则与代码解耦。规则文件的根元素是 <digester-rules>,内部用 <pattern value="servers"> 这类元素声明匹配路径,每个 pattern 下挂具体规则元素。写入 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>

逐项说明:<pattern value="servers"> 对应 Java 里的 addObjectCreate("servers", ArrayList.class) 等调用;<object-create-rule> 等价于 addObjectCreate(pattern, Class);<set-properties-rule/> 等价于 addSetProperties(pattern);<set-next-rule methodname="add"/> 等价于 addSetNext(pattern, "add")。写入输入文件 servers.xml(含三条 server 记录):

<?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>

通过 FromXmlRulesModule 加载该文件并解析,写入 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); }
    }
}

编译运行(servers.xml 内容为三条 server 记录):

cd /opt/digester-cfg
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}

规则文件可用的元素集合(对应各 addXxx 规则 API)如下:

规则元素对应 API作用
object-create-ruleaddObjectCreate进入 pattern 时创建对象
set-properties-ruleaddSetProperties按属性名匹配 setter 灌值
set-property-ruleaddSetProperty用子元素 name/value 指定属性
set-next-ruleaddSetNext把当前对象挂到父对象
call-method-ruleaddCallMethod调用方法,可带子元素参数
call-param-rule / object-param-ruleaddCallParam / addObjectParam为方法调用提供参数
node-create-ruleaddNodeCreate创建 DOM Node
factory-create-ruleaddFactoryCreate用自定义工厂创建对象
bean-property-setter-ruleaddBeanPropertySetter用元素体设置同名 Bean 属性
set-nested-properties-ruleaddSetNestedProperties设置嵌套/内层属性
set-top-rule / set-root-ruleaddSetTop / addSetRoot把对象压入/弹出栈顶或根

<pattern> 还支持 include 引入其他规则文件、alias 定义别名,便于大项目的规则复用。要点:规则文件名与路径以代码里 loadXMLRules(new File("digester-rules.xml")) 传入的为准,DTD 仅是文档约束、不参与运行时校验。

十、参考链接

  1. Apache Commons Digester 官方主页:https://commons.apache.org/proper/commons-digester/
  2. Digester 3.2 API 文档(含 setFeature/setProperty 等签名):https://commons.apache.org/proper/commons-digester/apidocs/index.html
  3. digester-rules DTD(规则文件结构定义):https://commons.apache.org/proper/commons-digester/dtds/digester-rules-3.0.dtd
  4. Maven 中央仓库 3.2 目录(含 with-deps.jar):https://repo1.maven.org/maven2/org/apache/commons/commons-digester3/3.2/
  5. SAX ErrorHandler 规范:https://docs.oracle.com/en/java/javase/17/docs/api/java.xml/org/xml/sax/ErrorHandler.html
  6. XML 解析安全(XXE 与外部实体):https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
  7. JAXP 特性与属性常量(disallow-doctype-decl、accessExternalDTD 等):https://docs.oracle.com/en/java/javase/17/docs/api/java.xml/javax/xml/XMLConstants.html
  8. Apache Commons BeanUtils 文档(属性注入底层实现):https://commons.apache.org/proper/commons-beanutils/