Apache Commons Validator 1.11.0 快速入门与集成示例
以 1.11.0 版本为例讲解 Apache Commons Validator 的 Maven 集成步骤、常用校验器最小示例与旧式 XML 规则配置进阶用法
Apache Commons Validator 1.11.0 快速入门与集成示例
目标:10 分钟内完成 Apache Commons Validator 1.11.0 的工程集成,跑通邮箱、URL、IP 地址、通用校验、日期解析与检查位六大类示例,并验证旧式 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(示例工程根目录)
二、组件定位与核心能力
Apache Commons Validator 是一套纯 JVM 数据校验工具库,官方对其定位是「为客户端与服务端数据校验提供构建积木,可独立使用,也可配合 Struts 等框架」。它的能力分成两大块:
org.apache.commons.validator.routines包:现代化、可直接实例化的校验器集合(邮箱、URL、IP、域名、日期/日历、货币、百分比、整数/长整数、信用卡号、各类检查位算法),绝大多数项目只需要这一层;org.apache.commons.validator包:以 XML 规则文件(validation.xml)驱动表单级校验的旧式框架(ValidatorResources/Form/Field),也就是 Struts 1 时代那套机制的内核。
版本选择上,1.11.0 是 1.x 系列当前最新稳定版(2026-07-31 发布),官方 RELEASE-NOTES 注明 Java 8 or later is required,因此从 JDK 8 到 JDK 21 都可运行,本文使用 JDK 17。
💡 技术拓展:代码校验 vs XML 规则校验如何选?
| 维度 | 代码方式(routines 包) | XML 方式(ValidatorResources) |
|---|---|---|
| 学习成本 | 低,单个类即用 | 高,需理解 FormSet/Form/Field/Var 体系 |
| 规则可维护性 | 规则内聚在代码中 | 规则外置 XML,可热调整 |
| 额外依赖 | 仅主 jar | 需 digester/logging/beanutils/collections |
| 典型场景 | 单值校验、微服务入参检查 | 表单引擎、遗留 Struts 风格系统 |
本文示例两种方式都会实测;现代代码优先用 routines 包。
三、五分钟上手:Maven 集成与第一个示例
3.1 引入依赖
创建工程目录并编写 POM:
# mkdir -p /work/demo/src/main/java /work/demo/src/main/resources
/work/demo/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>com.example.validator</groupId>
<artifactId>commons-validator-demo</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>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.11.0</version>
</dependency>
</dependencies>
<build>
<finalName>commons-validator-demo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>Demo</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
注意两点:groupId 是 commons-validator 而非 org.apache.commons;POM 不需要额外声明其他依赖,用 mvn dependency:tree 可看到自动带入的四个传递依赖:
# mvn -B dependency:tree
[INFO] --- dependency:3.7.0:tree (default-cli) @ commons-validator-demo ---
[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
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
3.2 第一个示例程序
/work/demo/src/main/java/Demo.java,一次性覆盖六大类 API:
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.routines.DateValidator;
import org.apache.commons.validator.routines.EmailValidator;
import org.apache.commons.validator.routines.InetAddressValidator;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
import java.util.Date;
import java.util.Locale;
public class Demo {
public static void main(String[] args) {
System.out.println("==== [1] EmailValidator ====");
EmailValidator ev = EmailValidator.getInstance();
System.out.println("isValid(user@example.com) = " + ev.isValid("user@example.com"));
System.out.println("isValid(first.last@sub.example.com) = " + ev.isValid("first.last@sub.example.com"));
System.out.println("isValid(user@[192.168.1.2]) = " + ev.isValid("user@[192.168.1.2]"));
System.out.println("isValid(user@@example.com) = " + ev.isValid("user@@example.com"));
System.out.println("isValid(user@localhost) = " + ev.isValid("user@localhost"));
System.out.println("allowLocal=true user@localhost = " + EmailValidator.getInstance(true).isValid("user@localhost"));
System.out.println("==== [2] UrlValidator ====");
UrlValidator uv = new UrlValidator();
System.out.println("isValid(https://commons.apache.org/proper/commons-validator/) = "
+ uv.isValid("https://commons.apache.org/proper/commons-validator/"));
System.out.println("isValid(ftp://ftp.example.com/pub/file.txt) = " + uv.isValid("ftp://ftp.example.com/pub/file.txt"));
System.out.println("isValid(www.apache.org) = " + uv.isValid("www.apache.org"));
System.out.println("ALLOW_ALL_SCHEMES ldap://example.com = "
+ new UrlValidator(UrlValidator.ALLOW_ALL_SCHEMES).isValid("ldap://example.com"));
System.out.println("==== [3] InetAddressValidator ====");
InetAddressValidator iv = InetAddressValidator.getInstance();
System.out.println("IPv4 192.168.1.1 = " + iv.isValidInet4Address("192.168.1.1"));
System.out.println("IPv4 256.1.1.1 = " + iv.isValidInet4Address("256.1.1.1"));
System.out.println("IPv6 ::1 = " + iv.isValidInet6Address("::1"));
System.out.println("IPv6 ::ffff:192.168.1.1 = " + iv.isValidInet6Address("::ffff:192.168.1.1"));
System.out.println("==== [4] GenericValidator ====");
System.out.println("isBlankOrNull(\"\") = " + GenericValidator.isBlankOrNull(""));
System.out.println("isBlankOrNull(\" \") = " + GenericValidator.isBlankOrNull(" "));
System.out.println("isInt(123) = " + GenericValidator.isInt("123"));
System.out.println("isInt(12.3) = " + GenericValidator.isInt("12.3"));
System.out.println("isDate(2026-10-15) = " + GenericValidator.isDate("2026-10-15", "yyyy-MM-dd", true));
System.out.println("==== [5] DateValidator ====");
DateValidator dv = DateValidator.getInstance();
Date d = dv.validate("15/10/2026", "dd/MM/yyyy", Locale.US);
System.out.println("parse 15/10/2026 (dd/MM/yyyy, US) = " + dv.format(d, "yyyy-MM-dd"));
System.out.println("==== [6] LuhnCheckDigit ====");
System.out.println("isValid(4417123456789113) = " + LuhnCheckDigit.LUHN_CHECK_DIGIT.isValid("4417123456789113"));
System.out.println("isValid(4111111111111112) = " + LuhnCheckDigit.LUHN_CHECK_DIGIT.isValid("4111111111111112"));
System.out.println("commons-validator 1.11.0 OK");
}
}
3.3 构建与运行
# cd /work/demo && mvn -B clean package
输出节选(实测末尾):
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing /work/demo/target/commons-validator-demo.jar with /work/demo/target/commons-validator-demo-1.0.0-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.691 s
[INFO] Finished at: 2026-10-15T01:33:54Z
[INFO] ------------------------------------------------------------------------
# java -Dfile.encoding=UTF-8 -jar target/commons-validator-demo.jar
==== [1] EmailValidator ====
isValid(user@example.com) = true
isValid(first.last@sub.example.com) = true
isValid(user@[192.168.1.2]) = true
isValid(user@@example.com) = false
isValid(user@localhost) = false
allowLocal=true user@localhost = true
==== [2] UrlValidator ====
isValid(https://commons.apache.org/proper/commons-validator/) = true
isValid(ftp://ftp.example.com/pub/file.txt) = true
isValid(www.apache.org) = false
ALLOW_ALL_SCHEMES ldap://example.com = true
==== [3] InetAddressValidator ====
IPv4 192.168.1.1 = true
IPv4 256.1.1.1 = false
IPv6 ::1 = true
IPv6 ::ffff:192.168.1.1 = true
==== [4] GenericValidator ====
isBlankOrNull("") = true
isBlankOrNull(" ") = true
isInt(123) = true
isInt(12.3) = false
isDate(2026-10-15) = true
==== [5] DateValidator ====
parse 15/10/2026 (dd/MM/yyyy, US) = 2026-10-15
==== [6] LuhnCheckDigit ====
isValid(4417123456789113) = true
isValid(4111111111111112) = false
commons-validator 1.11.0 OK
验证结果:BUILD SUCCESS 且全部 24 行断言输出符合预期。有两个值得注意的行为:user@[192.168.1.2] 这种 IPv4 域字面量默认被接受;user@localhost 默认拒绝、把 allowLocal 打开后才接受——这正是 1.11.0 的双开关设计(详见下节与配置详解篇)。
四、常用校验器逐个体验
4.1 EmailValidator:邮箱与三个开关
EmailValidator 通过 getInstance() 获取单例,另有 getInstance(boolean allowLocal) 与 getInstance(boolean allowLocal, boolean allowTld) 两个重载,含义分别是「允许本地域名(localhost 等)」与「允许纯 TLD 顶级域作为域名」。实测:
EmailValidator ev = EmailValidator.getInstance();
System.out.println(ev.isValid("user+tag@example.com")); // true
System.out.println(ev.isValid("\"john doe\"@example.com")); // true(引号本地部分)
System.out.println(ev.isValid("user@[::1]")); // false(IPv6 字面量域不被支持)
System.out.println(EmailValidator.getInstance(true).isValid("user@localhost")); // true
System.out.println(EmailValidator.getInstance(true).isValid("user@myhost.local")); // false(.local 不在内置本地 TLD)
System.out.println(EmailValidator.getInstance(true, true).isValid("user@com")); // true(allowTld)
全部实测输出:true、true、false、true、false、true。要点提示:默认实例即拒绝 localhost、user@example..com、user@@example.com 等非法形态;引号包裹的本地部分(如 "john doe"@example.com)在 1.11.0 中是合法的,这一点与很多自研正则的直觉相反。
4.2 UrlValidator:scheme 开关组合
UrlValidator def = new UrlValidator(); // 仅 http/https/ftp
UrlValidator all = new UrlValidator(UrlValidator.ALLOW_ALL_SCHEMES);
UrlValidator local = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS);
UrlValidator two = new UrlValidator(UrlValidator.ALLOW_2_SLASHES);
UrlValidator noFrag = new UrlValidator(UrlValidator.NO_FRAGMENTS);
对 gopher://example.com、http://local.server/path、http://www.apache.org//double、http://www.apache.org/index.html#top 四个样本分别用五个实例实测,输出依次为:false/true(ALL_SCHEMES 放行 gopher)、false/true(LOCAL_URLS 放行本地主机)、false/true(2_SLASHES 放行双斜杠路径)、true/false(NO_FRAGMENTS 拒绝锚点)。完整的开关矩阵与逐项回显见配置参数详解篇。
4.3 InetAddressValidator:IPv4/IPv6
InetAddressValidator iv = InetAddressValidator.getInstance();
iv.isValidInet4Address("192.168.1.1"); // true
iv.isValidInet4Address("01.02.03.04"); // false(拒绝前导零)
iv.isValidInet6Address("::ffff:192.168.1.1"); // true(混合 IPv6)
iv.isValidInet6Address("2001:db8::8a2e:370::7334"); // false(两个 ::)
实测输出为 true、false、true、false。要点提示:前导零拒绝是 1.11.0 的安全语义(避免八进制歧义),老代码若依赖 01.02.03.04 会开始返回 false。
4.4 GenericValidator 与日期校验器
GenericValidator 是一个纯静态工具类,把常用检查聚合起来(isBlankOrNull/isEmail/isUrl/isInt/isLong/isFloat/isDouble/isDate/isInRange/isCreditCard/matchRegexp 等),内部委托给对应的 routines 校验器。日期解析用 DateValidator:
DateValidator dv = DateValidator.getInstance();
Date d = dv.validate("15/10/2026", "dd/MM/yyyy", Locale.US); // 2026-10-15
dv.validate("15-10-2026", "yyyy-MM-dd"); // null(与 pattern 不符)
dv.validate("2026-02-30", "yyyy-MM-dd"); // null(不存在日期)
GenericValidator.isDate("2026-13-01", "yyyy-MM-dd", true); // false
实测输出:第一个表达式格式化回 2026-10-15,其余三个均为 null/false。要点提示:1.11.0 的日期校验器对底层 SimpleDateFormat 固定调用了 setLenient(false)(源码 365/390 行硬编码),因此 2026-02-30 这类「溢出日期」无论构造参数如何都会被拒绝,不会像原生 SimpleDateFormat 那样滚到 3 月 2 日。
4.5 检查位:从信用卡号到 ISBN
LuhnCheckDigit.LUHN_CHECK_DIGIT.isValid("4417123456789113"); // true
LuhnCheckDigit.LUHN_CHECK_DIGIT.isValid("4111111111111112"); // false
ISBNCheckDigit.ISBN_CHECK_DIGIT.isValid("9780306406157"); // true(ISBN-13)
ISBNCheckDigit.ISBN_CHECK_DIGIT.isValid("0306406152"); // true(ISBN-10)
new IBANCheckDigit().isValid("GB82WEST12345698765432"); // true
new ISINCheckDigit().isValid("US0378331005"); // true
实测输出依次为 true、false、true、true、true、true。所有检查位算法实现统一的 CheckDigit 接口(isValid/calculate),Mod11、EAN-13 等更多算法的实测见常用 API 篇。
五、进阶:XML 规则配置方式
5.1 前置事实:没有现成的 validator-rules.xml
很多资料会提到 validator-rules.xml,需要澄清:该文件是 Struts 1 随包发布的资源,commons-validator 自身(包括 1.11.0 的 jar 与官方 bin 包)从不携带它;1.11.0 的 jar 内只有 DTD 与 digester 映射规则,且旧版自带的 FieldChecks 回调类也已移除。因此 XML 方式需要自己定义回调类并在 validation.xml 的 <global> 段注册动作。这套机制在 JDK 17 下实测完整可用,下面是可复现的最小实现。
5.2 定义规则文件
/work/demo/src/main/resources/validation.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.4.0//EN"
"http://commons.apache.org/dtds/validation_1_4_0.dtd">
<form-validation>
<global>
<validator name="required"
classname="MyChecks"
method="validateRequired"
methodParams="java.lang.Object,org.apache.commons.validator.ValidatorAction,org.apache.commons.validator.Field,java.util.List"
msg="errors.required"/>
<validator name="minLength"
classname="MyChecks"
method="validateMinLength"
methodParams="java.lang.Object,org.apache.commons.validator.ValidatorAction,org.apache.commons.validator.Field,java.util.List"
msg="errors.minlength"/>
<validator name="email"
classname="MyChecks"
method="validateEmail"
methodParams="java.lang.Object,org.apache.commons.validator.ValidatorAction,org.apache.commons.validator.Field,java.util.List"
msg="errors.email"/>
<validator name="intRange"
classname="MyChecks"
method="validateIntRange"
methodParams="java.lang.Object,org.apache.commons.validator.ValidatorAction,org.apache.commons.validator.Field,java.util.List"
msg="errors.intRange"/>
<validator name="mask"
classname="MyChecks"
method="validateMask"
methodParams="java.lang.Object,org.apache.commons.validator.ValidatorAction,org.apache.commons.validator.Field,java.util.List"
msg="errors.invalid"/>
<constant>
<constant-name>postCodeMask</constant-name>
<constant-value>^\d{6}$</constant-value>
</constant>
</global>
<formset>
<form name="orderForm">
<field property="customerName" depends="required,minLength">
<arg key="label.customerName" position="0"/>
<var><var-name>minlength</var-name><var-value>3</var-value></var>
</field>
<field property="email" depends="required,email">
<arg key="label.email" position="0"/>
</field>
<field property="age" depends="required,intRange">
<arg key="label.age" position="0"/>
<var><var-name>min</var-name><var-value>18</var-value></var>
<var><var-name>max</var-name><var-value>65</var-value></var>
</field>
<field property="postCode" depends="required,mask">
<arg key="label.postCode" position="0"/>
<var><var-name>mask</var-name><var-value>${postCodeMask}</var-value></var>
</field>
</form>
</formset>
</form-validation>
5.3 回调类
/work/demo/src/main/java/MyChecks.java,静态方法签名固定为四参数(bean、动作、字段、错误列表),框架按 methodParams 反射调用:
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericTypeValidator;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.util.ValidatorUtils;
import java.util.List;
public final class MyChecks {
public static Boolean validateRequired(Object bean, ValidatorAction va, Field field, List<Object> errors) {
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
return !GenericValidator.isBlankOrNull(value);
}
public static Boolean validateMinLength(Object bean, ValidatorAction va, Field field, List<Object> errors) {
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
if (GenericValidator.isBlankOrNull(value)) {
return Boolean.TRUE;
}
int min = Integer.parseInt(field.getVarValue("minlength"));
return GenericValidator.minLength(value, min);
}
public static Boolean validateEmail(Object bean, ValidatorAction va, Field field, List<Object> errors) {
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
if (GenericValidator.isBlankOrNull(value)) {
return Boolean.TRUE;
}
return GenericValidator.isEmail(value);
}
public static Boolean validateIntRange(Object bean, ValidatorAction va, Field field, List<Object> errors) {
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
if (GenericValidator.isBlankOrNull(value)) {
return Boolean.TRUE;
}
Integer i = GenericTypeValidator.formatInt(value);
if (i == null) {
return Boolean.FALSE;
}
int min = Integer.parseInt(field.getVarValue("min"));
int max = Integer.parseInt(field.getVarValue("max"));
return GenericValidator.isInRange(i, min, max);
}
public static Boolean validateMask(Object bean, ValidatorAction va, Field field, List<Object> errors) {
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
if (GenericValidator.isBlankOrNull(value)) {
return Boolean.TRUE;
}
return GenericValidator.matchRegexp(value, field.getVarValue("mask"));
}
}
/work/demo/src/main/java/OrderBean.java:
public class OrderBean {
private String customerName;
private String email;
private String age;
private String postCode;
public OrderBean(String customerName, String email, String age, String postCode) {
this.customerName = customerName;
this.email = email;
this.age = age;
this.postCode = postCode;
}
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getAge() { return age; }
public void setAge(String age) { this.age = age; }
public String getPostCode() { return postCode; }
public void setPostCode(String postCode) { this.postCode = postCode; }
}
5.4 驱动校验
/work/demo/src/main/java/XmlProbe.java:
import org.apache.commons.validator.Validator;
import org.apache.commons.validator.ValidatorResult;
import org.apache.commons.validator.ValidatorResults;
import org.apache.commons.validator.ValidatorResources;
import java.io.InputStream;
import java.util.Iterator;
public class XmlProbe {
public static void main(String[] args) throws Exception {
try (InputStream forms = XmlProbe.class.getResourceAsStream("/validation.xml")) {
ValidatorResources resources = new ValidatorResources(forms);
run(resources, "第一轮:全部非法",
new OrderBean(" ", "not-an-email", "abc", "12345"));
run(resources, "第二轮:全部合法",
new OrderBean("张三丰", "user@example.com", "30", "100081"));
}
}
static void run(ValidatorResources resources, String round, OrderBean bean) throws Exception {
System.out.println("---- " + round + " ----");
Validator validator = new Validator(resources, "orderForm");
validator.setParameter(Validator.BEAN_PARAM, bean);
ValidatorResults results = validator.validate();
for (Object pn : results.getPropertyNames()) {
String prop = (String) pn;
ValidatorResult vr = results.getValidatorResult(prop);
StringBuilder sb = new StringBuilder();
for (Iterator<String> it = vr.getActions(); it.hasNext();) {
String action = it.next();
sb.append(action).append("=").append(vr.isValid(action) ? "PASS" : "FAIL").append(" ");
}
System.out.println(String.format("%-14s %s", prop, sb.toString().trim()));
}
}
}
生成 classpath 清单并运行:
# mvn -q -B compile dependency:build-classpath -Dmdep.outputFile=cp.txt
# java -Dfile.encoding=UTF-8 -cp "target/classes:$(cat cp.txt)" XmlProbe
实测输出(完整):
---- 第一轮:全部非法 ----
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
验证结果:两轮共 16 个断言全部符合预期——required 失败后同字段后续动作短路(customerName 只剩 required=FAIL);空字符串对非 required 动作放行(回调里自行返回 TRUE,这是 Struts 体系的经典约定);${postCodeMask} 常量替换在 mask 动作中生效。
5.5 实测中踩到的三个坑
第一,<constant> 必须用子元素 <constant-name>/<constant-value>,写成属性会被 DTD 校验拒绝,实测报错(java.util.logging 输出):
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".
第二,<global> 内元素顺序必须先全部 <validator> 再 <constant>,否则 DTD 报 The content of element type "global" must match "(validator*,constant*)"。
第三,回调抛出的任何异常都会被 ValidatorAction.executeValidationMethod 捕获并记为该动作 FAIL,同时打印 SEVERE: Unhandled exception thrown during validation 日志——实测把未替换的 ${postCodeMask} 当正则传入时即出现该行为,排查 XML 方式问题时优先看这个日志。
六、集成要点与工程建议
- 依赖体积:主 jar 约 190 KB,四个传递依赖中 beanutils 与 collections 较大;若确认不用 XML 方式,可用
<exclusions>排除 digester/logging 减少体积。 - 打包:
maven-shade-plugin生成 fat jar 实测可直接java -jar运行;Spring Boot 项目可直接依赖,无需额外适配。 - 单例使用:
EmailValidator.getInstance()、DateValidator.getInstance()等均返回共享实例,示例工程直接复用即可;带自定义开关的场景用getInstance(allowLocal, allowTld)等重载。 - JDK 兼容:POM 中把
maven.compiler.source/target改为 8 即可在 JDK 8 环境编译运行(组件本身只要求 Java 8),本文为演示 JDK 17 采用 17。 - 编码:涉及中文数据的解析(如日期 pattern 含中文)建议运行时统一
-Dfile.encoding=UTF-8。
七、常见问题(FAQ)
1. 为什么 user@[192.168.1.2] 合法而 user@[::1] 不合法?
EmailValidator 的域名分支支持 IPv4 点分十进制域字面量(带方括号),IPv6 域字面量的判断分支在 1.11.0 实测不可达(正则只匹配 IPv4 形态),因此 IPv6 字面量一律返回 false。这是组件现状,如需支持应在业务层先行解析。
2. 引号本地部分 "john doe"@example.com 居然是 true?
实测确实如此,1.11.0 的本地部分模式允许 RFC 5322 风格的 quoted-string。是否放行属于业务策略,可在校验前自行加一层白名单逻辑。
3. XML 方式一定要写回调类吗?
是的。1.11.0 不再提供 FieldChecks,required/email 等内置动作也不随包内置,必须在 <global> 注册自己的 Validator 动作(本文 5.2/5.3 节给了完整可跑实现)。
4. Validator 执行时抛 IllegalArgumentException:bean 之类错误?
setParameter 的第一个参数必须用常量 Validator.BEAN_PARAM(值为 java.lang.Object),框架按「参数类型类名」装配,手写字符串极易写错。
5. 如何校验表单里的一组字段而不是单个值?
用第五节的 XML 方式:一个 form 定义多个 field,validator.validate() 返回按字段组织的 ValidatorResults,逐字段取 ValidatorResult.isValid(action) 判断。
6. Gradle 项目怎么引依赖?
implementation 'commons-validator:commons-validator:1.11.0',坐标与 Maven 完全相同。
八、参考链接
- 官方用户指南:https://commons.apache.org/proper/commons-validator/userguide/index.html
- 1.11.0 API 文档:https://commons.apache.org/proper/commons-validator/apidocs/index.html
- Maven 坐标页:https://mvnrepository.com/artifact/commons-validator/commons-validator
- 官方下载与 Release Notes:https://downloads.apache.org/commons/validator/
- 本专栏配套文档:《Ubuntu 22.04 安装 Apache Commons Validator 指南》《Apache Commons Validator 1.11.0 配置参数详解》《Apache Commons Validator 1.11.0 常用 API 与校验器详解》