Apache BVal 配置参数详解

详解 Apache BVal 3.0.2 配置:消息插值与默认消息覆盖、EL 实测行为、分组/级联/容器元素、编程式配置 API、bval-extras 扩展约束与类型限制,全部实测。

Apache BValBVal2026/10/13

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.checkdigitABANumber CUSIP EAN13 IBAN ISBN10 Luhn Sedol Verhoeff各类校验位算法
constraints.creditcardAmericanExpress Diners Discover Mastercard Visa银行卡号(正则 + 校验位)
constraints.fileDirectory NotDirectory Symlink文件系统类型
constraints.netDomain 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 读取)。

十、参考链接

  1. Apache BVal 项目主页:https://bval.apache.org/
  2. Jakarta Bean Validation 3.0 规范:https://jakarta.ee/specifications/bean-validation/3.0/
  3. BVal 源码(消息文件与实现):https://github.com/apache/bval
  4. Maven 中央仓库 bval-jsr:https://repo1.maven.org/maven2/org/apache/bval/bval-jsr/
  5. Maven 中央仓库 bval-extras:https://repo1.maven.org/maven2/org/apache/bval/bval-extras/