Apache BVal 快速入门与集成示例
Apache BVal 3.0.2 快速入门:Maven 与手动双 jar 两种引入,示例覆盖内置约束回显、分组与级联、容器元素校验、bval-extras 扩展约束,JDK 17 实测。
Apache BVal 快速入门与集成示例
Apache BVal 是 Jakarta Bean Validation 的开源实现:在 JavaBean 上标注约束注解,调用
Validator即可拿到约束违反清单。本篇以 BVal 3.0.2 为例,覆盖 Maven 与手动双 jar 两种引入方式、内置约束与 bval-extras 扩展约束的实测回显,JDK 17 下全部真实运行。
一、环境信息
- 操作系统: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 最新稳定版,2026-10 实测)
- 运行统一带
LANG=C.UTF-8与-Dfile.encoding=UTF-8(中文输出需要) - 实验工程:
/opt/bval-demo
二、前置条件
java -version 2>&1 | head -1; mvn -version 2>&1 | head -1
回显(实测):
openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.6.3
JDK 11 及以上即可(BVal 3.0.2 字节码为 Java 11,MANIFEST 实测 Build-Jdk-Spec: 11)。
三、依赖引入
3.1 方式 A:Maven(推荐)
<dependency>
<groupId>org.apache.bval</groupId>
<artifactId>bval-jsr</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.bval</groupId>
<artifactId>bval-extras</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
实测依赖树(mvn dependency:tree):
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
jakarta.validation-api 必须显式声明:bval-jsr 把它标为 provided(预期由容器提供),独立应用不写就编译不过。运行期最小只需 bval-jsr + jakarta.validation-api 双 jar(bval-extras 仅在使用其扩展约束时需要,实测见第五节)。
3.2 方式 B:手动双 jar
从 Maven Central 下载 bval-jsr-3.0.2.jar(748810 字节)与 jakarta.validation-api-3.0.2.jar(93298 字节):
javac -encoding UTF-8 -cp lib/jakarta.validation-api-3.0.2.jar -d classes src/demo/BvalDemo.java
LANG=C.UTF-8 java -Dfile.encoding=UTF-8 -cp "classes:lib/bval-jsr-3.0.2.jar:lib/jakarta.validation-api-3.0.2.jar" demo.BvalDemo
编译只需规范 API jar,运行需实现 jar(bval-jsr 自带 META-INF/services/jakarta.validation.spi.ValidationProvider,buildDefaultValidatorFactory() 经 SPI 自动发现 ApacheValidationProvider,无需手写注册文件)。完整下载与 sha1 校验流程见《Ubuntu 22.04 安装 Apache BVal 指南》第五节。
四、核心 API 速览
| 类/接口 | 作用 |
|---|---|
jakarta.validation.Validation | 引导入口,构建 ValidatorFactory(SPI 自动发现或 byProvider 显式指定) |
ValidatorFactory | 工厂,创建 Validator,线程安全、应用级单例 |
Validator | 执行校验:validate(obj)(整对象)、validateProperty、validateValue |
ConstraintViolation<T> | 单条违反:getPropertyPath() 路径、getMessage() 消息、getInvalidValue() 无效值 |
jakarta.validation.constraints.* | 内置约束注解(NotNull NotBlank Size Min Max Pattern Email 等) |
org.apache.bval.extras.constraints.* | BVal 扩展约束(银行卡、IBAN、ISBN、IP 等 18 个) |
内置约束三要素:message(消息模板,支持 {min}/{value} 等属性插值)、groups(分组)、payload(元数据)。
五、集成示例:两个类跑通核心能力
5.1 示例一:内置约束全流程
package demo;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.constraints.*;
import java.util.Set;
public class BvalDemo {
public static class User {
@NotNull(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度须在 {min}-{max} 之间")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不合法")
private String email;
@Min(value = 18, message = "年龄不能小于 {value}")
@Max(value = 120, message = "年龄不能大于 {value}")
private Integer age;
@Pattern(regexp = "1[3-9]\\d{9}", message = "手机号格式不合法")
private String phone;
public User(String username, String email, Integer age, String phone) {
this.username = username; this.email = email; this.age = age; this.phone = phone;
}
public String getUsername() { return username; }
public String getEmail() { return email; }
public Integer getAge() { return age; }
public String getPhone() { return phone; }
}
public static void main(String[] args) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
System.out.println("ValidatorFactory 实现类: " + factory.getClass().getName());
User bad = new User("ab", "not-an-email", 15, "12345");
Set<ConstraintViolation<User>> violations = validator.validate(bad);
System.out.println("非法对象约束违反数: " + violations.size());
for (ConstraintViolation<User> v : violations) {
System.out.println(" [" + v.getPropertyPath() + "] " + v.getMessage()
+ " | 无效值 = " + v.getInvalidValue());
}
User good = new User("zhangsan", "zhangsan@example.com", 30, "13800138000");
System.out.println("合法对象约束违反数: " + validator.validate(good).size());
factory.close();
}
}
运行回显(实测):
ValidatorFactory 实现类: org.apache.bval.jsr.ApacheValidatorFactory
非法对象约束违反数: 4
[age] 年龄不能小于 18 | 无效值 = 15
[username] 用户名长度须在 3-20 之间 | 无效值 = ab
[phone] 手机号格式不合法 | 无效值 = 12345
[email] 邮箱格式不合法 | 无效值 = not-an-email
合法对象约束违反数: 0
要点:一个对象五类约束同时校验、四条违反全部命中且消息模板插值正确;validate() 返回 Set,条目顺序不固定(上面输出即两次采集顺序不同的实例);合法对象零违反。
5.2 示例二:分组、级联与容器元素
package demo;
import jakarta.validation.*;
import jakarta.validation.constraints.*;
import java.util.List;
import java.util.Set;
public class QuickstartDemo {
public interface Basic {}
public interface Strict {}
public static class Order {
@NotNull(groups = {Basic.class}, message = "订单号不能为空")
@Pattern(regexp = "ORD-\\d{6}", groups = {Strict.class}, message = "订单号须形如 ORD-999999")
private String orderNo;
@NotEmpty(groups = {Basic.class}, message = "商品列表不能为空")
private List<@NotBlank(message = "商品名不能为空白") String> items;
@Valid
@NotNull(groups = {Basic.class}, message = "收货地址不能为空")
private Address addr;
public Order(String orderNo, List<String> items, Address addr) {
this.orderNo = orderNo; this.items = items; this.addr = addr;
}
public String getOrderNo() { return orderNo; }
public List<String> getItems() { return items; }
public Address getAddr() { return addr; }
}
public static class Address {
@NotBlank(message = "城市不能为空")
private String city;
public Address(String city) { this.city = city; }
public String getCity() { return city; }
}
public static void main(String[] args) {
// 显式指定 BVal Provider(classpath 有多个实现时消歧)
ValidatorFactory factory = Validation.byProvider(
org.apache.bval.jsr.ApacheValidationProvider.class)
.configure().buildValidatorFactory();
Validator validator = factory.getValidator();
Order bad = new Order(null, List.of("", "咖啡"), new Address(""));
Set<ConstraintViolation<Order>> v1 = validator.validate(bad, Basic.class);
System.out.println("[Basic 组] violations = " + v1.size());
for (ConstraintViolation<Order> v : v1) {
System.out.println(" [" + v.getPropertyPath() + "] " + v.getMessage());
}
Set<ConstraintViolation<Order>> v2 = validator.validate(bad, Strict.class);
System.out.println("[Strict 组] violations = " + v2.size());
Set<ConstraintViolation<Order>> v3 = validator.validate(bad, Default.class, Basic.class);
System.out.println("[Default+Basic 组] violations = " + v3.size());
factory.close();
}
}
运行回显(实测):
[Basic 组] violations = 1
[orderNo] 订单号不能为空
[Strict 组] violations = 0
[Default+Basic 组] violations = 3
三个实测结论:orderNo=null 只触发 Basic 组的 @NotNull,null 不触发 @Pattern(Pattern 对 null 视为有效,规范行为);联合校验 Default.class + Basic.class 时,级联对象(addr.city)与容器元素(items[0])上的 Default 组约束同时生效,共 3 条。级联与容器元素的逐条路径回显(addr.city、items[0].<list element>)见《Apache BVal 配置参数详解》5.2 节。
5.3 示例三:bval-extras 扩展约束
package demo;
import jakarta.validation.*;
import jakarta.validation.ConstraintViolation;
import org.apache.bval.extras.constraints.creditcard.Visa;
import org.apache.bval.extras.constraints.checkdigit.IBAN;
import org.apache.bval.extras.constraints.net.InetAddress;
import org.apache.bval.extras.constraints.file.Directory;
import java.io.File;
import java.util.Set;
public class ExtrasDemo {
public static class Payment {
@Visa(message = "不是合法的 Visa 卡号")
private String card;
@IBAN(message = "不是合法的 IBAN")
private String iban;
@InetAddress(message = "不是合法的 IP 地址")
private String ip;
@Directory(message = "路径不是目录")
private File dir; // file 包约束只支持 java.io.File,不支持 String
public Payment(String card, String iban, String ip, File dir) {
this.card = card; this.iban = iban; this.ip = ip; this.dir = dir;
}
public String getCard() { return card; }
public String getIban() { return iban; }
public String getIp() { return ip; }
public File getDir() { return dir; }
}
public static void main(String[] args) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
File home = new File(System.getProperty("user.home"));
Payment bad = new Payment("1234567890123456", "DE8937040044053201300X", "999.999.1.1", home);
Set<ConstraintViolation<Payment>> v1 = validator.validate(bad);
System.out.println("[bad] violations = " + v1.size());
for (ConstraintViolation<Payment> v : v1) {
System.out.println(" [" + v.getPropertyPath() + "] " + v.getMessage());
}
Payment good = new Payment("4111111111111111", "DE89370400440532013000", "192.168.1.1", home);
System.out.println("[good] violations = " + validator.validate(good).size());
factory.close();
}
}
运行回显(实测):
[bad] violations = 4
[card] {org.apache.bval.extras.constraints.checkdigit.Luhn.message}
[card] must match the following regular expression: ^(4)(\d{12}|\d{15})$
[ip] 不是合法的 IP 地址
[iban] 不是合法的 IBAN
[good] violations = 0
要点:@Visa 内部由「正则 + Luhn 校验位」两个子约束组合而成,卡号两道都不满足时各报一条——注意即使 @Visa 上写了自定义 message,产出消息的也是内部子约束(Luhn 的默认消息 key {...Luhn.message} 原样输出、正则报其固定英文消息),这与常规约束「message 即所得」不同;合法 Visa 测试号、标准德国 IBAN、内网 IP 与真实目录路径全部通过。据此对银行卡类场景更建议用 @Luhn + 业务正则自行组合,消息可控。
六、注意事项
- jakarta vs javax:3.0.x 用
jakarta.validation.*,2.0.x 用javax.validation.*,版本、依赖、import 三者必须配套。 - Factory 单例:
ValidatorFactory/Validator创建成本高且线程安全,全局建一次复用,不要每请求新建。 - 消息插值限制:BVal 默认插值器支持
{min}/{value}模板参数,但不求值 EL——${validatedValue}会原样输出(实测),展示无效值用getInvalidValue()。 - bval-extras 的类型限制:
file包约束(@Directory等)只接受java.io.File,标注String启动即报No compliant ... ConstraintValidator found(实测)。 - Set 无序:违反清单顺序不稳定,展示前按
getPropertyPath()排序。 - 中文环境:运行带
LANG=C.UTF-8与-Dfile.encoding=UTF-8;BVal 无内置中文语言包,中文消息靠注解message或应用侧ValidationMessages_zh_CN.properties。
七、参考链接
- Apache BVal 项目主页:https://bval.apache.org/
- 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/
- Jakarta Bean Validation 3.0 规范:https://jakarta.ee/specifications/bean-validation/3.0/
- 系列篇:《Ubuntu 22.04 安装 Apache BVal 指南》《Apache BVal 配置参数详解》《Apache BVal 常用 CLI 命令与工具详解》