1. 概述

本文将深入探讨 Apache BVal 库对 Java Bean Validation 规范(JSR 349) 的实现。作为 Java 开发者,掌握 Bean Validation 能有效提升数据校验的健壮性,而 BVal 提供了一套轻量级且功能完备的解决方案。

2. Maven 依赖

要在项目中使用 Apache BVal,首先在 pom.xml 中添加以下核心依赖:

<dependency>
    <groupId>org.apache.bval</groupId>
    <artifactId>bval-jsr</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>

若需使用 BVal 提供的扩展约束(如信用卡校验、IBAN 校验等),需额外引入可选依赖:

<dependency>
    <groupId>org.apache.bval</groupId>
    <artifactId>bval-extras</artifactId>
    <version>1.1.2</version>
</dependency>

✅ 最新版本可通过 Maven Central 获取:

3. 应用约束

BVal 实现了 javax.validation 包中的所有标准约束。直接在属性声明上添加注解 即可应用约束:

public class User {
    
    @NotNull
    private String email;
    
    private String password;

    @Size(min=1, max=20)
    private String name;

    @Min(18)
    private int age;

    // 标准构造器、getter/setter
}

⚠️ 注意:@NotNull 确保字段非空,@Size 限制字符串长度,@Min 设置数值最小值——这些都是 Bean Validation 的基础操作,老驱动应该秒懂。

4. 校验 Bean

4.1. 获取 ValidatorFactory

强烈建议全局复用 ValidatorFactory 实例,因为其创建过程开销较大:

ValidatorFactory validatorFactory 
  = Validation.byProvider(ApacheValidationProvider.class)
  .configure().buildValidatorFactory();

4.2. 获取 Validator

从工厂中获取 Validator 实例:

Validator validator = validatorFactory.getValidator();

线程安全设计Validator 实例可安全复用,无需重复创建。

Validator 提供三个核心校验方法:

  • validate():校验整个对象
  • validateProperty():校验单个属性
  • validateValue():校验属性值(未绑定到对象)

4.3. validate() API

全量校验对象的所有约束,返回 ConstraintViolation 集合:

@Test
public void givenUser_whenValidate_thenValidationViolations() {
    User user
      = new User("user@example.com", "pass", "nameTooLong_______________", 15);

    Set<ConstraintViolation<User>> violations = validator.validate(user);
    assertTrue("no violations", violations.size() > 0);
}

4.4. validateProperty() API

精准校验单个属性,避免全量校验的性能开销:

@Test
public void givenInvalidAge_whenValidateProperty_thenConstraintViolation() {
    User user = new User("user@example.com", "pass", "Ana", 12);

    Set<ConstraintViolation<User>> propertyViolations
      = validator.validateProperty(user, "age");
 
    assertEquals("size is not 1", 1, propertyViolations.size());
}

4.5. validateValue() API

预校验属性值,在赋值前检查合法性:

@Test
public void givenValidAge_whenValidateValue_thenNoConstraintViolation() {
    User user = new User("user@example.com", "pass", "Ana", 18);
    
    Set<ConstraintViolation<User>> valueViolations
      = validator.validateValue(User.class, "age", 20);
 
    assertEquals("size is not 0", 0, valueViolations.size());
}

4.6. 关闭 ValidatorFactory

使用完毕后必须关闭工厂,避免资源泄漏:

if (validatorFactory != null) {
    validatorFactory.close();
}

5. 非 JSR 约束

BVal 提供了 JSR 规范之外的扩展约束,增强校验能力:

核心扩展约束(bval-jsr

  • @Email:校验邮箱格式
  • @NotEmpty:确保非空(区别于 @NotNull

高级扩展约束(bval-extras

约束类型 示例注解 用途
数字格式 @IBAN 国际银行账号校验
@Isbn 标准书号校验
@EAN13 国际商品编码校验
信用卡校验 @Visa Visa 卡号校验
@Mastercard Mastercard 卡号校验
网络地址 @Domain 域名校验
@InetAddress IP 地址校验
文件系统 @Directory 校验是否为目录
@NotDirectory 校验是否为非目录

User 类中应用扩展约束:

public class User {
    
    @NotNull
    @Email
    private String email;
    
    @NotEmpty
    private String password;

    @Size(min=1, max=20)
    private String name;

    @Min(18)
    private int age;
    
    @Visa
    private String cardNumber = "";
    
    @IBAN
    private String iban = "";
    
    @InetAddress
    private String website = "";

    @Directory
    private File mainDirectory = new File(".");

    // 标准构造器、getter/setter
}

测试扩展约束:

@Test
public void whenValidateNonJSR_thenCorrect() {
    User user = new User("user@example.com", "pass", "Ana", 20);
    user.setCardNumber("1234");
    user.setIban("1234");
    user.setWebsite("10.0.2.50");
    user.setMainDirectory(new File("."));
    
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user,"iban");
 
    assertEquals("size is not 1", 1, violations.size());
 
    violations = validator.validateProperty(user,"website");
 
    assertEquals("size is not 0", 0, violations.size());

    violations = validator.validateProperty(user, "mainDirectory");
 
    assertEquals("size is not 0", 0, violations.size());
}

⚠️ 踩坑提醒:非 JSR 约束虽强大,但会降低代码可移植性。若未来需切换其他 JSR 实现(如 Hibernate Validator),需重构这些约束。

6. 自定义约束

6.1. 定义约束注解

创建 @Password 注解,声明密码校验规则:

@Constraint(validatedBy = { PasswordValidator.class })
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {
    String message() default "Invalid password";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int length() default 6;

    int nonAlpha() default 1;
}

6.2. 实现校验逻辑

通过 ConstraintValidator 接口实现校验逻辑:

public class PasswordValidator 
  implements ConstraintValidator<Password, String> {

    private int length;
    private int nonAlpha;

    @Override
    public void initialize(Password password) {
        this.length = password.length();
        this.nonAlpha = password.nonAlpha();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        if (value.length() < length) {
            return false;
        }
        int nonAlphaNr = 0;
        for (int i = 0; i < value.length(); i++) {
            if (!Character.isLetterOrDigit(value.charAt(i))) {
                nonAlphaNr++;
            }
        }
        if (nonAlphaNr < nonAlpha) {
            return false;
        }
        return true;
    }
}

6.3. 应用自定义约束

User 类中使用 @Password

@Password(length = 8)
private String password;

6.4. 测试自定义约束

@Test
public void givenInvalidPassword_whenValidatePassword_thenConstraintViolation() {
    User user = new User("user@example.com", "password", "Ana", 20);
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user, "password");
 
    assertEquals(
      "message incorrect",
      "Invalid password", 
      violations.iterator().next().getMessage());
}

@Test
public void givenValidPassword_whenValidatePassword_thenNoConstraintViolation() {
    User user = new User("user@example.com", "password#", "Ana", 20);
        
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user, "password");
    assertEquals("size is not 0", 0, violations.size());
}

7. 总结

本文系统介绍了 Apache BVal 的核心功能:

  • ✅ 标准 JSR 349 约束的实现
  • ✅ 高级扩展约束(邮箱、信用卡、IBAN 等)
  • ✅ 自定义约束开发实践
  • ✅ 线程安全的校验器使用模式

完整示例代码见 GitHub 仓库


原始标题:Intro to Apache BVal