Apache Commons Validator 1.11.0 配置参数详解

系统梳理 Apache Commons Validator 1.11.0 在环境、校验器实例开关与 XML 规则三个层面的全部配置项并附实测矩阵

Apache Commons ValidatorApache Commons Validator2026/10/15

Apache Commons Validator 1.11.0 配置参数详解

目标:完整梳理 Commons Validator 1.11.0 的三类配置入口(JVM 环境、校验器实例开关、XML 规则文件),并对每个开关给出实测验证矩阵,已在 Ubuntu 22.04(x86_64)+ Temurin JDK 17.0.20.1 + Maven 3.9.16 环境实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS
  • 架构:x86_64
  • JDK:Temurin OpenJDK 17.0.20.1
  • 构建工具:Apache Maven 3.9.16
  • 组件版本:Apache Commons Validator 1.11.0
  • Maven 坐标:commons-validator:commons-validator:1.11.0
  • 工作目录:/work/demo(示例工程根目录,全部探针类位于 src/main/java)

二、配置体系总览

Commons Validator 是「无配置文件、纯代码开关」的库,这一点与数据库连接池类组件完全不同。它的配置入口分三层:

层级配置手段典型示例生效范围
环境层JVM 参数、Locale、依赖编排-Dfile.encoding=UTF-8、Locale.US整个进程
实例层构造参数与 getInstance 重载UrlValidator.ALLOW_ALL_SCHEMES单个校验器实例
规则层validation.xml + 回调类<field depends="required,email">一次表单校验

💡 技术拓展:同为 Apache Commons 组件,「配置详解」的写法因组件而异——像 Commons IO 的 IOUtils 只有方法参数可谈;而 Validator 的「配置」主要体现为校验器构造开关与 XML 规则要素。本文按上述三层展开,所有结论均来自 1.11.0 实测。

三、环境层配置

3.1 JDK 版本兼容

官方 RELEASE-NOTES 原文:This is a feature and maintenance release. Java 8 or later is required.。因此:

  • 编译期:组件本身按 Java 8 字节码发布,工程 POM 可将 maven.compiler.source/target 设为 8~17 之间任意版本;
  • 运行期:JDK 8/11/17/21 均可,本文全部探针在 JDK 17 下编译运行通过;
  • 唯一注意点:旧式 XML 方式依赖的 commons-digester 2.1 与 commons-beanutils 1.11.0 在 JDK 17 下实测无兼容问题(见第十章实测回显)。

3.2 字符编码与 Locale

  • 编码:实测 Ubuntu 环境 mvn -version 显示 platform encoding: ANSI_X3.4-1968,处理中文 pattern(如 yyyy年MM月dd日)或中文数据时统一加 -Dfile.encoding=UTF-8,本文所有 java 命令均带该参数;
  • Locale:数值与日期校验器(DateValidator、CurrencyValidator 等)是 Locale 敏感的,调用时显式传 Locale.US、Locale.GERMANY 等,不要依赖系统默认值,具体实测见 8.2 节。

3.3 Maven 依赖编排

commons-validator:1.11.0 自动带入四个传递依赖。若确认只用 routines 包代码校验,可排除以减小体积:

<dependency>
  <groupId>commons-validator</groupId>
  <artifactId>commons-validator</artifactId>
  <version>1.11.0</version>
  <exclusions>
    <exclusion>
      <groupId>commons-digester</groupId>
      <artifactId>commons-digester</artifactId>
    </exclusion>
    <exclusion>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

实测依赖树(未排除时):

[INFO] com.example.validator:commons-validator-demo:jar:1.0.0
[INFO] \- commons-validator:commons-validator:jar:1.11.0:compile
[INFO]    +- commons-beanutils:commons-beanutils:jar:1.11.0:compile
[INFO]    +- commons-digester:commons-digester:jar:2.1:compile
[INFO]    +- commons-logging:commons-logging:jar:1.4.0:compile
[INFO]    \- commons-collections:commons-collections:jar:3.2.2:compile

要点提示:XML 规则方式(第十章)依赖 digester 解析与 beanutils 取值,排除后这两者将不可用,需按需取舍。

四、UrlValidator:scheme 与结构开关

4.1 开关常量一览

常量含义默认行为
ALLOW_ALL_SCHEMES放行任意 scheme(默认仅 http/https/ftp)不开启
ALLOW_LOCAL_URLS放行单标签主机名等本地 URL不开启
ALLOW_2_SLASHES放行路径中连续双斜杠 //不开启
NO_FRAGMENTS禁止锚点片段(# 及其后内容)锚点默认允许

开关通过位或组合传入构造器:new UrlValidator(UrlValidator.ALLOW_ALL_SCHEMES | UrlValidator.ALLOW_LOCAL_URLS)。

4.2 实测矩阵

探针对默认实例与四个开关实例逐一验证(-> 后为实测返回值):

default        http://www.apache.org                                -> true
default        https://commons.apache.org/proper/commons-validator/ -> true
default        ftp://ftp.example.com/pub/file.txt                   -> true
default        http://www.apache.org:8080/index.html?q=validator#top -> true
default        https://example.com/a/b?x=1&y=2                      -> true
default        gopher://example.com                                 -> false
default        ldap://example.com/dc=example,dc=com                 -> false
default        www.apache.org                                       -> false
default        http://local.server/path                             -> false
default        http://www.apache.org//double                        -> false
default        http://bad url.com                                   -> false
ALL_SCHEMES    gopher://example.com                                 -> true
ALL_SCHEMES    ldap://example.com/dc=example,dc=com                 -> true
ALL_SCHEMES    custom+app://host/path                               -> false
LOCAL_URLS     http://local.server/path                             -> false
LOCAL_URLS     http://myserver:8080/app                             -> true
LOCAL_URLS     http://www.apache.org                                -> true
2_SLASHES      http://www.apache.org//double                        -> true
2_SLASHES      http://www.apache.org/single                         -> true
NO_FRAGMENTS   http://www.apache.org/index.html#top                 -> false
NO_FRAGMENTS   http://www.apache.org/index.html                     -> true
COMBO          myapp://internal.host:8080//a/b#frag                 -> true

实测发现两个反直觉点:

  • ALLOW_ALL_SCHEMES 下 custom+app://host/path 仍为 false:scheme 正则不允许 + 字符,含加号的自定义协议需业务层先行放行;
  • ALLOW_LOCAL_URLS 下 http://local.server/path 仍为 false:local.server 不是单标签主机,其 TLD server 不在 1.11.0 内置 TLD 表中(实测 DomainValidator.isValidTld("server") = false,而 app 为 true,内置 generic TLD 共 1127 个),既非合法 TLD 又非本地主机,两个分支都不放行。

4.3 分解校验方法

isValidScheme/isValidAuthority/isValidPath/isValidQuery/isValidFragment 五个方法在 1.11.0 中是 protected,需要通过子类暴露才能单独调用:

static class ExposedUrlValidator extends UrlValidator {
    boolean checkScheme(String s)    { return isValidScheme(s); }
    boolean checkAuthority(String s) { return isValidAuthority(s); }
    boolean checkPath(String s)      { return isValidPath(s); }
    boolean checkQuery(String s)     { return isValidQuery(s); }
    boolean checkFragment(String s)  { return isValidFragment(s); }
}

默认实例实测:

isValidScheme(http)        = true
isValidScheme(gopher)      = false
isValidAuthority(www.apache.org:8080) = true
isValidAuthority(bad_host) = false
isValidPath(/proper/commons-validator/) = true
isValidPath(//double)      = false
isValidQuery(?q=validator) = true
isValidFragment(#top)      = true

要点提示:分解方法适合定位「一个 URL 为什么被拒」,例如 //double 路径默认拒绝正好解释了 4.2 节 ALLOW_2_SLASHES 的存在意义。

五、EmailValidator:allowLocal 与 allowTld

5.1 开关语义

构造方式allowLocalallowTld效果
getInstance()falsefalse最严格:域名必须是「合法 TLD 结尾的普通域名」
getInstance(true)truefalse额外放行内置本地 TLD(localdomain、localhost)
getInstance(true, true)truetrue额外放行「纯 TLD 域名」如 user@com、user@cn

5.2 实测

默认实例 19 个样本:

"user@example.com"               -> true
"first.last@sub.example.com"     -> true
"user+tag@example.com"           -> true
"user_123@example.co.uk"         -> true
"user@[192.168.1.2]"             -> true
"user@[::1]"                     -> false
"user@192.168.1.2"               -> false
""john doe"@example.com"         -> true
"user@localhost"                 -> false
"user.name@"                     -> false
"@example.com"                   -> false
"user@example..com"              -> false
"user@@example.com"              -> false
"user name@example.com"          -> false
"user@.example.com"              -> false
"user@example.com."              -> false
"user@example.c"                 -> false
"user@-example.com"              -> false
"user@com"                       -> false

开关实例:

---- getInstance(true)(allowLocal) ----
user@localhost    -> true
user@myhost.local -> false
user@com          -> true

---- getInstance(true, true)(allowLocal + allowTld) ----
user@com -> true
user@cn  -> true
user@localhost -> true

实测要点:

  • IPv4 域字面量 user@[192.168.1.2] 无需任何开关即为 true;但 IPv6 字面量 user@[::1] 为 false——1.11.0 的 IPv6 域字面量判断分支实测不可达,这是组件现状,需要时应在业务层先解析;
  • 引号本地部分 ""john doe"@example.com" 是 true,1.11.0 允许 RFC 风格 quoted-string 本地部分;
  • user@myhost.local 为 false:.local 不在内置本地 TLD 数组中(内置仅 localdomain,localhost,见 7.1 节),需用 7.3 节的 LOCAL_PLUS 扩展才能放行。

六、InetAddressValidator:行为即配置

该类没有构造开关,配置体现在「校验语义」上。全部实测:

IPv4     192.168.1.1                          -> true
IPv4     255.255.255.0                        -> true
IPv4     0.0.0.0                              -> true
IPv4     127.0.0.1                            -> true
IPv4     256.1.1.1                            -> false
IPv4     1.2.3                                -> false
IPv4     1.2.3.4.5                            -> false
IPv4     1.2.3.4a                             -> false
IPv4     01.02.03.04                          -> false
IPv4     1.2.3.256                            -> false
IPv6     ::1                                  -> true
IPv6     fe80::1                              -> true
IPv6     2001:db8::8a2e:370:7334              -> true
IPv6     2001:0db8:85a3:0000:0000:8a2e:0370:7334 -> true
IPv6     ::ffff:192.168.1.1                   -> true
IPv6     2001:db8::8a2e:370::7334             -> false
IPv6     1:2:3:4:5:6:7:8:9                    -> false
IPv6     12345::                              -> false
IPv6     ::ffff:999.1.1.1                     -> false
合并       ::1                                  -> true
合并       192.168.1.1                          -> true
合并       example.com                          -> false

要点提示:01.02.03.04 被拒绝(前导零安全语义,避免八进制歧义);::ffff:192.168.1.1 这类 IPv4 映射地址按 IPv6 校验通过;合并方法 isValid 对任一地址族通过即返回 true。

七、DomainValidator:TLD 表与扩展开关

7.1 内置 TLD 数组规模

1.11.0 把 TLD 列表内置为四个只读数组,可通过 DomainValidator.getTLDEntries(ArrayType) 读取,实测规模:

GENERIC_RO 条目数  = 1127
COUNTRY_CODE_RO 条目数 = 309
INFRASTRUCTURE_RO  = arpa
LOCAL_RO           = localdomain,localhost
GENERIC 最后 5 个   = zara,zero,zip,zone,zuerich

💡 技术拓展:四类 TLD 数组的分工

数组实测规模含义示例
GENERIC_RO1127通用顶级域com、info、xxx、app
COUNTRY_CODE_RO309国家和地区顶级域cn、uk、de
INFRASTRUCTURE_RO1(arpa)基础设施域arpa
LOCAL_RO2本地保留域localdomain、localhost

.xxx 属于 GENERIC_RO 内置成员(实测 isValidTld("xxx") = true 且 generic 分类命中),这是它与 localdomain 等的本质区别:前者是真实 gTLD,后者只在 allowLocal 开启时生效。

7.2 getInstance(allowLocal) 实测

isValidTld(xxx             ) = true  generic=true  cc=false local=false
isValidTld(cn              ) = true  generic=false cc=true  local=false
isValidTld(localhost       ) = false generic=false cc=false local=true
isValidTld(example         ) = false generic=false cc=false local=false
isValidTld(COM             ) = true  generic=true  cc=false local=false
isValidTld(CN              ) = true  generic=false cc=true  local=false
---- getInstance(true) 后 ----
isValidTld(localhost)      = true
isValidTld(example)        = false
isValidLocalTld(localhost) = true

实测要点:分类判断大小写不敏感(COM/CN 均通过);example、invalid、test 虽是 RFC 2606 保留域,但 1.11.0 的 LOCAL_RO 并不包含它们,任何开关下都返回 false。

7.3 updateTLDOverride:四个扩展开关

DomainValidator.updateTLDOverride(ArrayType, String[]) 允许在运行时扩充或收缩 TLD 表,ArrayType 共四个值:GENERIC_PLUS(追加 gTLD)、GENERIC_MINUS(移除 gTLD)、LOCAL_PLUS(追加本地 TLD)、LOCAL_MINUS(移除本地 TLD)。

实测一:追加自定义 gTLD 与本地 TLD:

DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"mybrand"});
DomainValidator.updateTLDOverride(ArrayType.LOCAL_PLUS, new String[]{"mycorp.internal"});
isValidTld(mybrand)        = true
isValidGenericTld(mybrand) = true
isValidTld(mycorp.internal)= false
isValidLocalTld(mycorp.internal) = true
isValidTld(com)            = true

(mycorp.internal 的 isValidTld 为 false 是因为默认实例 allowLocal=false,getInstance(true) 下为 true。)

实测二:用 GENERIC_MINUS 关闭 .xxx:

DomainValidator.updateTLDOverride(ArrayType.GENERIC_MINUS, new String[]{"xxx"});
移除后 isValidTld(xxx) = false
对照 isValidTld(com)  = true

要点提示:updateTLDOverride 必须在第一次 getInstance() 之前调用(单例缓存 TLD 表后才注册会失效),且这是进程级全局修改,多业务共存的进程要谨慎使用——对内容平台而言,GENERIC_MINUS 关闭 .xxx 域是 1.11.0 提供的现成开关。

八、数值、日期与货币校验器配置

8.1 DateValidator / CalendarValidator

可配置点为构造参数与调用参数两层:构造器 new DateValidator(boolean strict, int dateStyle),getInstance() 返回 strict=true 的共享实例;调用层可传 pattern 与 Locale。实测:

---- DateValidator(getInstance 默认 strict=true) ----
validate(15/10/2026, dd/MM/yyyy, US) = 2026-10-15
validate(2026-10-15, yyyy-MM-dd)     = 2026/10/15
validate(15-10-2026, yyyy-MM-dd)     = null
validate(2026-02-30, yyyy-MM-dd)     = null
validate(10/15/2026, dd/MM/yyyy, US) = null
validate(15.10.2026, dd.MM.yyyy, DE) = 2026-10-15
lenient 2026-02-30 (回写格式化)      = null

两条重要实测结论:

  • pattern 与 Locale 决定解析成败:15-10-2026 对 yyyy-MM-dd 返回 null;德国区域 dd.MM.yyyy 正常解析;
  • new DateValidator(false, DateFormat.SHORT)(strict=false)下 2026-02-30 仍为 null。源码核实:AbstractCalendarValidator 第 365/390 行对底层 formatter 硬编码 setLenient(false),strict 参数只影响「解析结果回写比对」,因此溢出日期(2 月 30 日)在任何配置下都会被拒绝,不会滚到 3 月。

CalendarValidator 返回 java.util.GregorianCalendar,pattern 中文字面量在 UTF-8 下工作正常:

---- CalendarValidator ----
parse 类型 = java.util.GregorianCalendar
format 回写 = 2026年10月15日 13:45
format 反转 = 13:45 2026-10-15

8.2 CurrencyValidator

按 Locale 解析与格式化,实测发现一个隐蔽的配置陷阱:

---- CurrencyValidator ----
validate($1,234.56, US)  = 1234.56 (java.math.BigDecimal)
format(1234.56, US)      = $1,234.56
format(1234.56, DE)      = 1.234,56 €
validate(ASCII 空格写法, DE) = null
往返 validate(format 产物, DE) = 1234.56

德语区格式化结果 1.234,56 € 中的空格实际是不换行空格(U+00A0),手写 ASCII 空格的输入串解析直接失败返回 null;用 format() 产出的字符串做往返校验才通过。跨区域货币解析务必以组件自身 format 产物为准。

8.3 PercentValidator

默认实例的行为由底层 NumberFormat 的百分数格式决定,实测存在小数截断:

---- PercentValidator ----
validate(50%, US)   = 0.50 (java.math.BigDecimal)
validate(12.5%, US) = 0.12
format(0.125, US)   = 12%

实测解析链路:先按 DecimalFormat(小数位 0)解析,BigDecimalValidator.processParsedValue 以 ROUND_DOWN 把小数截断到 0 位,再乘 0.01——所以 12.5% 得到 0.12 而不是 0.125,format(0.125) 也只显示 12%。需要保留小数百分比时,应自定义 NumberFormat 后按对应构造器创建 PercentValidator 实例配套使用,而不是改默认单例。

8.4 LongValidator / IntegerValidator

---- LongValidator / IntegerValidator ----
validate(123456789012, US) = 123456789012 (java.lang.Long)
isInRange(5, 1, 10)  = true
isInRange(11, 1, 10) = false
validate(abc, US)    = null
validate(42, US)     = 42 (java.lang.Integer)

数值校验器的配置就是 Locale 与取值范围,非法输入统一返回 null(配合 GenericValidator.isInRange 做区间判断)。

九、检查位(CheckDigit)配置

9.1 接口与 1.11.0 现状

所有检查位算法实现统一接口 CheckDigit:boolean isValid(String) 与 String calculate(String)(抛 CheckDigitException)。注意 1.11.0 的 AbstractCheckDigit 已相当精简,老资料中的 generate() 辅助方法已不存在,生成校验位请直接用 calculate 后自行拼接。

9.2 ModulusTenCheckDigit:通用模 10 构造器

构造器三参数为 ModulusTenCheckDigit(int[] positionWeight, boolean useRightPos, boolean sumWeightedDigits):权重数组、权重从右往左还是从左往右、以及是否对乘积做数位求和(Luhn/CUSIP 需要)。EAN-13 等价写法实测:

---- ModulusTenCheckDigit(通用模 10,权重可配;EAN-13 等价写法) ----
isValid(4006381333931)    = true
isValid(4006381333932)    = false
calculate(400638133393)   = 1

9.3 经典 Mod11 的 1.11.0 写法

1.11.0 不内置 Mod11 算法类,需继承 ModulusCheckDigit 自实现。关键是权重函数的 rightPos 语义:校验位自身位置为 1,数据位自右向左从 2 开始:

static final class Mod11CheckDigit extends ModulusCheckDigit {
    Mod11CheckDigit() { super(11); }

    @Override
    protected int weightedValue(int charValue, int leftPos, int rightPos) {
        // rightPos=1 是校验位自身位置(权重 1);数据位自右向左权重 2 到 10 循环
        int weight = rightPos;
        if (weight > 10) {
            weight = (weight - 2) % 9 + 2;
        }
        return charValue * weight;
    }
}

实测(036532 的经典 Mod11 校验位为 7):

---- Mod11(ModulusCheckDigit 子类自实现,权重 2..10 自右向左) ----
calculate(036532)         = 7
isValid(0365327)          = true
isValid(0365328)          = false

要点提示:若算法可能出现校验位 10(经典记法写作 X),还需覆写 toCheckDigit/toInt 处理 X 字符(ISBN10CheckDigit 即采用此做法),否则框架会抛 CheckDigitException: Invalid Check Digit Value =10——这是实测走过的弯路。

9.4 CreditCardValidator:卡种开关

new CreditCardValidator(int options) 用卡种常量位或组合(VISA、AMEX、MASTERCARD、DISCOVER),校验时先按前缀卡种匹配再跑 Luhn:

---- CreditCardValidator(卡种开关 + Luhn) ----
VISA+AMEX  6011111111111117 = false
全卡种     6011111111111117 = true
全卡种     4111111111111111 = true
全卡种     378282246310005  = true
全卡种     4111111111111112 = false

6011111111111117 是 Discover 测试卡号,Luhn 本身合法,仅因 VISA+AMEX 实例不含 DISCOVER 被拒——卡种开关与校验位是两层独立配置。

十、XML 规则配置详解

10.1 文档结构

validation.xml 根元素为 <form-validation>,下设 <global>(全局动作与常量)与 <formset>(表单集合),DOCTYPE 固定为 1.4.0 DTD(组件 jar 内置该 DTD,离线可解析)。DTD 对元素顺序有硬性要求:<global> 内必须先全部 <validator> 再 <constant>。

10.2 <global> 动作注册

每个校验动作由 <validator> 注册,四个属性:

属性含义本文取值
name动作名,供 depends 引用required、minLength、email、intRange、mask
classname回调类全限定名MyChecks(默认包)
method回调静态方法名validateRequired 等
methodParams方法签名(逗号分隔类名)java.lang.Object,org.apache.commons.validator.ValidatorAction,org.apache.commons.validator.Field,java.util.List

实测注意事项:<constant> 必须用子元素写法,写成属性会被 DTD 拒绝,实测报错(输出节选):

SEVERE: Parse Error at line 32 column 70: Attribute "constant-name" must be declared for element type "constant".
org.xml.sax.SAXParseException; lineNumber: 32; columnNumber: 70; Attribute "constant-name" must be declared for element type "constant".

10.3 <formset>/<form>/<field> 要素

要素/属性作用本文取值
<form name>表单名,new Validator(resources, formName) 按此查找orderForm
<field property>绑定 bean 属性名,框架经 getter 取值customerName、email、age、postCode
depends逗号分隔的动作链,按序执行,首个失败后本字段短路required,minLength 等
<arg key position>错误消息参数占位label.customerName 等
<var>/<var-name>/<var-value>传给回调的参数,field.getVarValue(name) 读取minlength=3、min=18、max=65、mask=${postCodeMask}

${常量名} 会在加载期替换为 <global> 中定义的 <constant> 值,实测 mask 动作收到的已是替换后的 ^\d{6}$(第二轮 100081 通过即为证据)。

10.4 回调约定

回调方法返回 Boolean:TRUE 记 PASS、FALSE 或抛异常记 FAIL。抛异常时框架打印 SEVERE 日志并继续其余字段(输出节选,实测把未替换的 ${postCodeMask} 当正则时触发):

SEVERE: Unhandled exception thrown during validation: Illegal repetition near index 2
${postCodeMask}
  ^
java.util.regex.PatternSyntaxException: Illegal repetition near index 2

10.5 端到端实测

驱动代码与完整回显(两轮共 16 个断言):

---- 第一轮:全部非法 ----
postCode       required=PASS  mask=FAIL
customerName   required=FAIL
email          required=PASS  email=FAIL
age            intRange=FAIL  required=PASS
---- 第二轮:全部合法 ----
postCode       required=PASS  mask=PASS
customerName   minLength=PASS  required=PASS
email          required=PASS  email=PASS
age            intRange=PASS  required=PASS

验证结果:required 失败后同字段后续动作短路;空值对非 required 动作放行(回调内自行返回 TRUE);常量替换、多动作依赖、范围校验全部按配置生效。完整的 validation.xml、回调类与驱动代码见本专栏快速入门篇第五节。

十一、常见问题(FAQ)

1. 为什么我的 URL 开了 ALLOW_ALL_SCHEMES 还是被拒? 检查 scheme 是否含 + 等非常规字符(实测 custom+app 被拒),以及主机名 TLD 是否在 1.11.0 内置表内(实测 server 不在、app 在)。

2. user@[::1] 为什么不合法? 1.11.0 的 IPv6 域字面量分支实测不可达,EmailValidator 只支持 IPv4 域字面量。需要 IPv6 时在业务层自行解析。

3. updateTLDOverride 调用后没生效? 必须在进程第一次 DomainValidator.getInstance() 之前调用;单例初始化后 TLD 表已固化。

4. 12.5% 解析成了 0.12? PercentValidator 默认实例按小数位 0 的 DecimalFormat 解析并 ROUND_DOWN 截断,属预期行为;需要小数百分比请自建 NumberFormat 与校验器实例。

5. 2026-02-30 想放行怎么办? 组件在任何配置下都拒绝溢出日期(formatter 硬编码非宽松模式)。业务上确需宽松语义时,用 java.time 自行解析后再交给其他校验。

6. XML 方式如何定位失败原因? 三个抓手:ValidatorResult.isValid(action) 逐动作判断;ValidatorAction 的 SEVERE 异常日志;DTD 报错行号。三者叠加足以定位绝大多数规则文件问题。

7. 排除 digester 后 XML 方式报 NoClassDefFoundError? XML 方式强依赖 digester/logging/beanutils/collections 四个包,要么不排除,要么彻底不用 XML 方式。

十二、参考链接