1. 简介

Yavi 是一个轻量级 Java 验证库,能以简洁优雅的方式确保对象状态的有效性。作为 Java 应用中的理想验证方案,它具备以下核心优势:

零反射设计:无需在待验证对象上添加注解,实现完全解耦
类型安全 API:编译时就能防止无效验证规则定义
全类型支持:兼容应用中定义的所有类型
丰富约束集:提供大量预定义约束,同时支持自定义扩展

本文将深入探讨 Yavi 的核心功能与实战技巧,助你高效处理各类验证场景。

2. 依赖配置

当前最新版本为 0.14.1,在 Maven 项目中添加以下依赖:

<dependency>
    <groupId>am.ik.yavi</groupId>
    <artifactId>yavi</artifactId>
    <version>0.14.1</version>
</dependency>

配置完成后即可开始使用。

3. 基础验证

3.1 简单值验证

针对 StringInteger 等基础类型,使用专用构建器创建验证器:

StringValidator<String> validator = StringValidatorBuilder.of("name", c -> c.notBlank())
  .build();

构建器参数说明:

  • 首参数:字段名称(用于错误报告)
  • 次参数:验证规则 Lambda 表达式

3.2 对象验证

更常见的场景是验证整个对象,使用 ValidatorBuilder 定义多字段规则:

public record Person(String name, int age) {}

Validator<Person> validator = ValidatorBuilder.of(Person.class)
  .constraint(Person::name, "name", c -> c.notBlank())
  .constraint(Person::age, "age", c -> c.positiveOrZero().lessThan(150))
  .build();

关键点

  • constraint() 参数依次为:字段 getter、字段名、验证规则
  • Yavi 自动校验 getter 返回类型与约束的兼容性(如 notBlank() 仅适用于字符串)

验证调用示例:

ConstraintViolations result = validator.validate(new Person("", 42));
assertFalse(result.isValid());

错误信息解析

assertEquals(1, result.size());
assertEquals("name", result.get(0).name());
assertEquals("charSequence.notBlank", result.get(0).messageKey());

3.3 嵌套对象验证

当对象包含其他对象时,使用 nest() 方法进行嵌套验证:

public record Name(String firstName, String surname) {}
public record Person(Name name, int age) {}

Validator<Name> nameValidator = ValidatorBuilder.of(Name.class)
  .constraint(Name::firstName, "firstName", c -> c.notBlank())
  .constraint(Name::surname, "surname", c -> c.notBlank())
  .build();

Validator<Person> personValidator = ValidatorBuilder.of(Person.class)
  .nest(Person::name, "name", nameValidator)
  .constraint(Person::age, "age", c -> c.positiveOrZero().lessThan(150))
  .build();

错误路径会自动拼接

assertEquals(2, result.size());
assertEquals("name.firstName", result.get(0).name());
assertEquals("name.surname", result.get(1).name());

3.4 跨字段验证

当验证规则依赖多个字段时,使用 constraintOnTarget()

record Range(int start, int end) {}

Validator<Range> validator = ValidatorBuilder.of(Range.class)
  .constraintOnTarget(range -> range.end > range.start, "end", "range.endGreaterThanStart",
    "\"end\" must be greater than \"start\"")
  .build();

自定义约束参数

  • 首参数:验证逻辑 Lambda
  • 次参数:字段名
  • 第三参数:错误消息键
  • 第四参数:默认错误消息

4. 自定义约束

4.1 内联自定义约束

通过 predicate() 方法快速定义临时约束:

Validator<Data> validator = ValidatorBuilder.of(Data.class)
  .constraint(Data::palindrome, "palindrome",
    c -> c.predicate(s -> validatePalindrome(s), "palindrome.valid", "\"{0}\" must be a palindrome"))
  .build();

4.2 可复用约束类

实现 CustomConstraint 接口创建可复用约束:

class PalindromeConstraint implements CustomConstraint<String> {
    @Override
    public boolean test(String input) {
        String reversed = new StringBuilder()
          .append(input)
          .reverse()
          .toString();
        return input.equals(reversed);
    }

    @Override
    public String messageKey() {
        return "palindrome.valid";
    }

    @Override
    public String defaultMessageFormat() {
        return "\"{0}\" must be a palindrome";
    }
}

使用方式

Validator<Data> validator = ValidatorBuilder.of(Data.class)
  .constraint(Data::palindrome, "palindrome", c -> c.predicate(new PalindromeConstraint()))
  .build();

5. 条件约束

5.1 基于上下文的约束

定义约束组(推荐使用枚举):

enum Action implements ConstraintGroup {
    CREATE,
    UPDATE,
    DELETE
}

使用 constraintOnCondition() 指定生效条件:

Validator<Person> validator = ValidatorBuilder.of(Person.class)
  .constraint(Person::name, "name", c -> c.notBlank())
  .constraintOnCondition(Action.UPDATE.toCondition(),
    b -> b.constraint(Person::id, "id", c -> c.notBlank()))
  .build();

验证时传入上下文

ConstraintViolations result = validator.validate(new Person(null, "Baeldung"), Action.UPDATE);

5.2 动态条件约束

通过 Lambda 定义复杂条件:

Validator<Person> validator = ValidatorBuilder.of(Person.class)
  .constraintOnCondition((person, ctx) -> person.id() != null,
    b -> b.constraint(Person::name, "name", c -> c.notBlank()))
  .build();

6. 方法参数验证

Yavi 支持在方法调用前验证参数,避免创建无效对象:

6.1 构造函数验证

Arguments2Validator<String, Integer, Person> validator = ArgumentsValidatorBuilder.of(Person::new)
  .builder(b -> b
    ._string(Arguments1::arg1, "name", c -> c.notBlank())
    ._integer(Arguments2::arg2, "age", c -> c.positiveOrZero())
  )
  .build();

验证调用

Validated<Person> result = validator.validate("", -1);

错误处理

assertFalse(result.isValid());
assertEquals(2, result.errors().size());
assertEquals("name", result.errors().get(0).name());
assertEquals("charSequence.notBlank", result.errors().get(0).messageKey());

6.2 普通方法验证

record Person(String name, int age) {
    boolean isOlderThan(int check) {
        return this.age > check;
    }
}

Arguments2Validator<Person, Integer, Boolean> validator = ArgumentsValidatorBuilder.of(Person::isOlderThan)
  .builder(b -> b
    ._integer(Arguments2::arg2, "age", c -> c.positiveOrZero())
  )
  .build();

调用方式

Person person = new Person("Baeldung", 42);
Validated<Boolean> result = validator.validate(person, -1);

7. 注解处理器

7.1 字段注解

使用 @ConstraintTarget 自动生成元类:

record Person(@ConstraintTarget String name, @ConstraintTarget int age) {}

简化验证器构建

Validator<Person> validator = ValidatorBuilder.of(Person.class)
  .constraint(_PersonMeta.NAME, c -> c.notBlank())
  .constraint(_PersonMeta.AGE, c -> c.positiveOrZero().lessThan(150))
  .build();

优势

  • 无需手动指定字段名和 getter
  • 字段不存在时编译报错

7.2 方法参数注解

使用 @ConstraintArguments 注解方法或构造函数:

record Person(String name, int age) {
    @ConstraintArguments
    Person {}

    @ConstraintArguments
    boolean isOlderThan(int check) {
        return this.age() > check;
    }
}

生成验证器

Arguments2Validator<String, Integer, Person> validator = ArgumentsValidatorBuilder.of(Person::new)
  .builder(b -> b
    .constraint(_PersonArgumentsMeta.NAME, c -> c.notBlank())
    .constraint(_PersonArgumentsMeta.AGE, c -> c.positiveOrZero())
  )
  .build();

8. 总结

Yavi 作为现代 Java 验证框架,通过类型安全 API、零反射设计和灵活的扩展机制,为开发者提供了高效可靠的验证解决方案。其核心优势包括:

🚀 简洁性:流式 API 构建验证规则,代码可读性强
🔧 灵活性:支持嵌套对象、跨字段验证和条件约束
高性能:编译时生成代码,避免运行时反射开销
🌐 生态集成:完美兼容 Spring 等主流框架

下次需要验证框架时,不妨尝试 Yavi,体验其带来的开发效率提升。所有示例代码可在 GitHub 获取。


原始标题:Validation Using Yavi | Baeldung