Apache BVal 配置参数详解
详解 Apache BVal 3.0.2 配置:消息插值与默认消息覆盖、EL 实测行为、分组/级联/容器元素、编程式配置 API、bval-extras 扩展约束与类型限制,全部实测。
Apache BVal 配置参数详解
本篇详解 Apache BVal 3.0.2 的配置面:消息插值参数(
{min}/{max}/{value}与 EL)、默认消息查看与覆盖、分组与级联、编程式配置 API、bval-extras扩展约束清单与类型限制。所有结论均在 Ubuntu 22.04(OpenJDK 17.0.20.1、Maven 3.6.3、BVal 3.0.2)下实测采集。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS,x86_64
- JDK:OpenJDK 17.0.20.1;构建工具:Apache Maven 3.6.3
- 组件版本:bval-jsr 3.0.2、bval-extras 3.0.2、jakarta.validation-api 3.0.2(Maven Central 实测最新稳定版)
- 实验工程:
/opt/bval-demo(依赖引入方式见《Ubuntu 22.04 安装 Apache BVal 指南》4.2 节) - 运行统一带
LANG=C.UTF-8与-Dfile.encoding=UTF-8(否则中文输出问号)
二、前置条件
沿用安装指南的 Maven 工程,三个依赖齐备:
cd /opt/bval-demo && mvn dependency:tree -DoutputFile=/tmp/tree.txt >/dev/null 2>&1; cat /tmp/tree.txt
回显(实测):
demo:bval-demo:jar:1.0.0
+- org.apache.bval:bval-jsr:jar:3.0.2:compile
+- org.apache.bval:bval-extras:jar:3.0.2:compile
\- jakarta.validation:jakarta.validation-api:jar:3.0.2:compile
三、参数查看方式:javap 与 jar tf
3.1 javap 看配置 API 真实签名
javap -classpath /root/.m2/repository/org/apache/bval/bval-jsr/3.0.2/bval-jsr-3.0.2.jar org.apache.bval.jsr.ConfigurationImpl | grep -E "public org.apache.bval|public jakarta|public void"
回显(实测,节选):
public org.apache.bval.jsr.ApacheValidatorConfiguration ignoreXmlConfiguration();
public org.apache.bval.jsr.ConfigurationImpl messageInterpolator(jakarta.validation.MessageInterpolator);
public org.apache.bval.jsr.ApacheValidatorConfiguration traversableResolver(jakarta.validation.TraversableResolver);
public org.apache.bval.jsr.ConfigurationImpl constraintValidatorFactory(jakarta.validation.ConstraintValidatorFactory);
public org.apache.bval.jsr.ApacheValidatorConfiguration parameterNameProvider(jakarta.validation.ParameterNameProvider);
public org.apache.bval.jsr.ApacheValidatorConfiguration clockProvider(jakarta.validation.ClockProvider);
public org.apache.bval.jsr.ApacheValidatorConfiguration addMapping(java.io.InputStream);
public org.apache.bval.jsr.ApacheValidatorConfiguration addProperty(java.lang.String, java.lang.String);
public jakarta.validation.MessageInterpolator getDefaultMessageInterpolator();
这是 BVal 可编程配置的全部入口:六个标准组件(插值器/遍历解析器/约束校验器工厂/参数名提供者/时钟提供者)+ XML 映射与属性,链式调用,均来自 jakarta.validation.Configuration 标准接口,BVal 无私有配置项。
3.2 jar tf 看消息文件与 schema
jar tf /root/.m2/repository/org/apache/bval/bval-jsr/3.0.2/bval-jsr-3.0.2.jar | grep -E "ValidationMessages|validation-configuration|DefaultConstraints"
回显(实测):
META-INF/validation-configuration-1.0.xsd
META-INF/validation-configuration-1.1.xsd
META-INF/validation-configuration-2.0.xsd
META-INF/validation-configuration-3.0.xsd
org/apache/bval/jsr/DefaultConstraints.properties
org/apache/bval/jsr/ValidationMessages.properties
org/apache/bval/jsr/ValidationMessages_de.properties
org/apache/bval/jsr/ValidationMessages_en.properties
org/apache/bval/jsr/ValidationMessages_es.properties
org/apache/bval/jsr/ValidationMessages_it.properties
要点:BVal 内置默认消息包(ValidationMessages.properties)与德/英/西/意四个语言包——没有中文包,中文消息只能靠应用侧 ValidationMessages_zh_CN.properties 或注解 message 自定义(见 4.4 节);validation-configuration-3.0.xsd 是 META-INF/validation.xml 的 schema,兼容 1.0~3.0 四代。
四、消息插值参数
4.1 注解自带参数:{min}/{max}/{value} 模板
约束注解的 message 支持把注解属性以 {属性名} 形式插值,实测(BvalDemo,自定义消息):
@Size(min = 3, max = 20, message = "用户名长度须在 {min}-{max} 之间")
@Min(value = 18, message = "年龄不能小于 {value}")
违反时输出(实测):
[username] 用户名长度须在 3-20 之间 | 无效值 = ab
[age] 年龄不能小于 18 | 无效值 = 15
{min}/{max}/{value} 均被注解属性值正确替换。
4.2 默认消息:22 条标准模板
去掉注解 message,BVal 用内置 ValidationMessages.properties(实测共 22 条 jakarta.validation.constraints.*.message),样例(grep 实测):
jakarta.validation.constraints.Email.message=must be a well-formed email address
jakarta.validation.constraints.Max.message=must be less than or equal to {value}
jakarta.validation.constraints.Min.message=must be greater than or equal to {value}
jakarta.validation.constraints.NotBlank.message=must not be blank
jakarta.validation.constraints.NotEmpty.message=must not be empty
jakarta.validation.constraints.DecimalMax.message=must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
跑一个无自定义消息的 Bean(三个约束均不带 message):
public static class Foo {
@Size(min = 3, max = 20)
private String name;
@Min(18)
private Integer age;
@DecimalMax("99.5")
private Double score;
}
违反输出(实测):
[name] size must be between 3 and 20
[age] must be greater than or equal to 18
[score] must be less than ${inclusive == true ? 'or equal to ' : ''}99.5
注意第三条:DecimalMax 默认消息里的 EL 表达式 ${inclusive == true ? 'or equal to ' : ''} 被原样输出、未求值——BVal 3.0.2 的默认 MessageInterpolator 不插值 EL(与 Hibernate Validator 行为不同)。含 EL 的默认消息(DecimalMax/DecimalMin)在生产中建议覆盖或改用 @Max/@Min。
4.3 ${validatedValue}:同样原样输出
规范中的 ${validatedValue} 表达式在 BVal 默认插值器下也不求值。实测(订单号 @Pattern 消息引用了它):
@Pattern(regexp = "ORD-\\d{6}", groups = {Strict.class}, message = "订单号须形如 ORD-999999,实际为 ${validatedValue}")
违反输出(实测):
[orderNo] 订单号须形如 ORD-999999,实际为 ${validatedValue}
${validatedValue} 原样出现。需要展示无效值时,用程序侧的 ConstraintViolation#getInvalidValue()(BvalDemo 已实测输出 无效值 = not-an-email)。
4.4 覆盖默认消息:ValidationMessages.properties
在 src/main/resources/ValidationMessages.properties 写同名 key 即可全局替换默认消息:
jakarta.validation.constraints.Size.message=长度必须在 {min} 到 {max} 之间
jakarta.validation.constraints.Min.message=年龄不能低于 {value}
重跑 4.2 节的 Foo,输出(实测,UTF-8):
[name] 长度必须在 3 到 20 之间
[age] 年龄不能低于 18
[score] must be less than ${inclusive == true ? 'or equal to ' : ''}99.5
Size/Min 已换成中文(模板参数照常插值),未覆盖的 DecimalMax 保持原样。优先级:注解 message > 应用侧 ValidationMessages.properties > BVal 内置默认。同理可加 ValidationMessages_zh_CN.properties 按 locale 区分。
4.5 bval-extras 默认消息:未本地化,key 原样输出
bval-extras 的约束没有默认消息资源文件。实测 @Visa(内部组合 Luhn 校验位算法)不传 message 时:
[card] {org.apache.bval.extras.constraints.checkdigit.Luhn.message}
消息 key 直接原样输出。结论:bval-extras 的单约束(@InetAddress/@IBAN 等)必须显式写 message 才有可读消息(实测生效);组合约束(如 @Visa)例外——外层 message 不覆盖内部子约束报告,详见 7.3 节。
五、分组、级联与容器元素配置
5.1 分组(groups)
约束默认属于 Default 组;validate(obj) 只校验 Default 组,validate(obj, groups...) 校验指定组。实测定义 Basic/Strict 两组的订单类:
@NotNull(groups = {Basic.class}, message = "订单号不能为空")
@Pattern(regexp = "ORD-\\d{6}", groups = {Strict.class}, message = "...")
private String orderNo;
对 orderNo = null 的对象分别校验(实测):
[Basic 组] violations = 1
[orderNo] 订单号不能为空
[Strict 组] violations = 0
[Default+Basic 组] violations = 3
三个实测结论:同一属性可按组挂不同约束;null 不触发 @Pattern(Pattern 对 null 视为有效,规范行为);validate(obj, Default.class, Basic.class) 联合校验时,级联对象与容器元素上的 Default 组约束同时生效(共 3 条 = orderNo 的 Basic 约束 + 商品名 @NotBlank + 收货城市 @NotBlank)。
5.2 级联(@Valid)与容器元素校验
@Valid
@NotNull(groups = {Basic.class}, message = "收货地址不能为空")
private Address addr;
@NotEmpty(groups = {Basic.class}, message = "商品列表不能为空")
private List<@NotBlank(message = "商品名不能为空白") String> items;
实测对 Address.city="" 与 items 含空白串的对象输出(实测):
[items[0].<list element>] 商品名不能为空白
[addr.city] 城市不能为空
@Valid 递归校验嵌套对象(路径 addr.city),List<@NotBlank String> 校验容器元素(路径 items[0].<list element>),均开箱即用,无额外配置。对 items=null, addr=null 的对象实测报 商品列表不能为空、收货地址不能为空 两条,@NotEmpty/@NotNull 均生效。
六、编程式配置 API
6.1 显式指定 BVal Provider
ValidatorFactory factory = Validation.byProvider(org.apache.bval.jsr.ApacheValidationProvider.class)
.configure().buildValidatorFactory();
Validator validator = factory.getValidator();
实测运行正常(QuickstartDemo,违反计数与消息与 buildDefaultValidatorFactory() 完全一致)。classpath 上存在多个 Bean Validation 实现时用它消歧;Validation.byDefaultProvider() 则按 SPI 发现顺序自动选择。
6.2 工厂配置链
标准链式配置(签名实测见 3.1 节):
ValidatorFactory factory = Validation.byProvider(ApacheValidationProvider.class)
.configure()
.messageInterpolator(...) // 自定义消息插值器
.parameterNameProvider(...) // 方法参数名(校验方法参数时)
.clockProvider(...) // @Past/@Future 的时钟源
.traversableResolver(...) // 属性可达性控制
.constraintValidatorFactory(...) // 校验器实例化(可接 DI 容器)
.buildValidatorFactory();
ignoreXmlConfiguration() 用于忽略 META-INF/validation.xml。XML 侧等价物是 classpath 上的 META-INF/validation.xml(schema 为 jar 内 validation-configuration-3.0.xsd),可配置默认 Provider、消息文件、约束映射;本篇以编程式为准,XML 仅在容器集成场景需要。
七、bval-extras 扩展约束清单与类型限制
7.1 约束注解清单(jar tf 实测)
| 包 | 注解 | 用途 |
|---|---|---|
constraints.checkdigit | ABANumber CUSIP EAN13 IBAN ISBN10 Luhn Sedol Verhoeff | 各类校验位算法 |
constraints.creditcard | AmericanExpress Diners Discover Mastercard Visa | 银行卡号(正则 + 校验位) |
constraints.file | Directory NotDirectory Symlink | 文件系统类型 |
constraints.net | Domain InetAddress | 域名 / IP 地址 |
7.2 @Directory 仅支持 java.io.File
@Directory 标注在 String 字段上时启动即失败(实测报错原文):
No compliant org.apache.bval.extras.constraints.file.Directory ConstraintValidator found
for annotated element of type java.lang.String
把字段类型改为 java.io.File 后运行正常(实测:非目录路径报「路径不是目录」,目录路径 0 违反)。file 包三个约束同理,只接受 File 类型元素。
7.3 组合约束的多次报告
@Visa 内部是「正则 + Luhn」两道校验,卡号 1234567890123456 同时不满足两道,实测产出两条违反:
[card] {org.apache.bval.extras.constraints.checkdigit.Luhn.message}
[card] must match the following regular expression: ^(4)(\d{12}|\d{15})$
注意:即使 @Visa 上显式写了自定义 message,产出消息的仍是内部子约束(Luhn 默认消息 key 原样输出、正则报固定英文消息)——组合约束的 message 不会覆盖子约束报告。@IP/@IBAN 这类单约束则「message 即所得」(实测自定义消息正常输出)。合法 Visa 测试号 4111111111111111 实测 0 违反。
八、生产配置模板
<!-- pom.xml 最小依赖集(独立应用) -->
<dependency>
<groupId>org.apache.bval</groupId>
<artifactId>bval-jsr</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<!-- 需要扩展约束再加 bval-extras:3.0.2 -->
// 全局工厂单例(线程安全,整个应用共享)
public final class V {
private static final Validator VALIDATOR =
Validation.byProvider(org.apache.bval.jsr.ApacheValidationProvider.class)
.configure()
.buildValidatorFactory()
.getValidator();
public static <T> Set<ConstraintViolation<T>> validate(T bean, Class<?>... groups) {
return VALIDATOR.validate(bean, groups);
}
}
配置要点回顾:注解 message 必写(尤其 bval-extras);中文消息靠应用侧 ValidationMessages*.properties;不依赖 EL 插值(${validatedValue} 与含 EL 的默认消息在 BVal 下原样输出);jakarta.validation-api 必须显式引入。
九、常见问题(FAQ)
Q1:为什么 {min} 插值正常而 ${inclusive == true ? ...} 原样输出?
BVal 默认 MessageInterpolator 实现了规范要求的 {注解属性} 模板插值,未启用 EL 表达式求值。规避方式:默认消息覆盖(4.4 节)或注解显式 message;需要展示无效值用 getInvalidValue()。
Q2:加了 ValidationMessages.properties 没生效?
位置必须在 src/main/resources 根下(编译后位于 classpath 根),key 必须是全限定注解类名 + .message(如 jakarta.validation.constraints.Size.message),改动后需重新 mvn compile 让资源进入 target/classes。同时注意它只影响未写 message 的注解,注解上的 message 优先级更高。
Q3:@Directory 标在 String 上启动报错?
见 7.2 节,file 包约束只支持 java.io.File,改类型或改用其他约束。
Q4:validate() 每次返回的违反顺序不一样?
validate() 返回 Set,无序。需要稳定展示时按 getPropertyPath() 排序后再输出。
Q5:中文消息输出问号?
locale 与文件编码问题:运行带 LANG=C.UTF-8 与 -Dfile.encoding=UTF-8,properties 文件保存为 UTF-8(JDK 9+ properties 直接按 UTF-8 读取)。
十、参考链接
- Apache BVal 项目主页:https://bval.apache.org/
- Jakarta Bean Validation 3.0 规范:https://jakarta.ee/specifications/bean-validation/3.0/
- BVal 源码(消息文件与实现):https://github.com/apache/bval
- Maven 中央仓库 bval-jsr:https://repo1.maven.org/maven2/org/apache/bval/bval-jsr/
- Maven 中央仓库 bval-extras:https://repo1.maven.org/maven2/org/apache/bval/bval-extras/