一佳技术

easy-validate 校验组件设计

本文档描述 easy-validate 组件的设计与实现。该组件将两个独立的校验引擎(FluentValidator 链式 API + Yavi 声明式 API)包在统一门面基类 ValidateSupport 下,通过同一套错误模型(ValidateError / ValidateException)暴露给业务代码。提供 13 个 Validator 实现(NotNull / NotBlank / Range / Regex / Exist / Length / EqualTo 等)覆盖常见业务场景。


一、背景与问题

1.1 为什么要封装两个校验引擎

Java 生态已有多个校验方案(JSR303 Bean Validation、FluentValidator、Yavi、Hutool Validator 等),Matrix Framework 需要满足两类场景:

场景适合的 API 风格选择
命令式/链式:字段按运行时条件动态校验Fluent API,链式方法调用FluentValidator
声明式/构建器:校验规则提前定义好,执行时只传入实体Builder 构建不可变 Validator 对象Yavi

easy-validate 把两者统一在 ValidateSupport 抽象基类下,业务方选择继承即可——不需要额外依赖两个引擎(两者都是 compile scope 硬依赖)。

1.2 与 JSR303 Bean Validation 的关系

FluentValidator 模式内置了 JSR303 注解校验(@NotNull / @Size / @Min 等),在链式 Validator 执行之前先跑一遍 HibernateValidator。但 Yavi 是纯编程式,不做注解。


二、组件定位与边界

定位:业务 Service / DomainService 的校验能力门面基类。继承 ValidateSupport 即可同时获得 Fluent 链式和 Yavi 声明式两种校验 API。

边界:

做不做
ValidateSupport 抽象基类(给业务代码继承)不做校验规则持久化(不靠 easy-store)
ValidatorDelegate 链式代理(Fluent 模式)不做国际化(错误消息只有模板)
13 个 Validator 实现(NotNull/Range/Regex/Exist 等)不做注解式规则自动发现(Validator 没加 @Component)
统一错误模型 ValidateError / ValidateException不替代 JSR303 Bean Validation(只是增强)
YaviValidatorBuilder 集成(声明式模式)不做远程 / 网络调用校验

约束:

  • FluentValidator 硬依赖(compile scope 无条件加载);Yavi 也是 compile scope,但自动配置有 @ConditionalOnClass 保护
  • FluentValidateException 标记 @Deprecated,应逐步统一到 ValidateException
  • 生产环境必须由应用自行提供 Hibernate Validator(JSR303 实现),否则 fluentHibernateValidator Bean 创建失败
  • 13 个 Validator 没加 @Component,SpringRegistry 自动发现机制形同虚设(但 Fluent 路径本来就是手动 new,影响不大)
  • RangeValidator.byError() 默认 min=0, max=Long.MAX_VALUE,范围检查靠自定义 message

三、总体架构

flowchart TD
    subgraph "业务层"
        A1["业务 Service / DomainService<br/>继承 ValidateSupport"]
    end

    subgraph "ValidateSupport 抽象基类"
        B1["validate(entity, Consumer<ValidatorDelegate>)<br/>Fluent 模式入口"]
        B2["validate(entity, am.ik.yavi.core.Validator<T>)<br/>Yavi 模式入口"]
        B3["validateOnly(entity, yaviValidator)<br/>Yavi 模式只校验不抛"]
        B4["check() → FluentValidator.checkAll()<br/>check(entity) → + HibernateSupportedValidator + 链式"]
    end

    subgraph "FluentValidator 引擎"
        C1["ValidatorDelegate<br/>链式代理(13 个 fluent 方法)"]
        C2["FluentValidator (百度开源)<br/>链式 + 注解校验"]
        C3["HibernateSupportedValidator<br/>JSR303 注解校验"]
        C4["SpringApplicationContextRegistry<br/>让 Fluent 从 Spring 发现 Validator"]
        C5["13 个 Validator 实现<br/>(全部 extends ValidatorHandler<T>)"]
    end

    subgraph "Yavi 引擎"
        D1["am.ik.yavi.core.Validator<T>(业务方自行构建)"]
        D2["ConstraintViolations → ValidateError[]<br/>Yavi 自动填充"]
    end

    subgraph "统一错误模型"
        E1["ValidateError<br/>field + message(final 字段,Serializable)"]
        E2["ValidateException<br/>RuntimeException + List<ValidateError>"]
        E3["FluentValidateException<br/>@Deprecated,继承 ValidateException"]
    end

    A1 --> B1 & B2
    B1 --> C1
    C1 --> C2
    B2 --> D1
    C2 --> C5
    C2 --> C3
    C2 --> C4
    C1 --> E2
    B2 --> E2
    D2 --> E1

两种模式的共存方式:完全独立互不干扰。FluentValidateConfiguration 永远加载(无条件);YaviValidateAutoConfiguration 是 @ConditionalOnClass 保护的空壳(Yavi 本身不需要 Spring 集成,占位而已)。业务方通过选择 ValidateSupport 的哪个 validate() 重载来决定用哪个引擎。


四、核心 API 草图

ValidateSupport(abstract,业务继承用)

// Fluent 模式:链式调用,传 Consumer 接收 ValidatorDelegate
protected <T> void validate(T entity, Consumer<ValidatorDelegate> consumer) {
    // 1. 跑 JSR303 注解校验(HibernateValidator)
    // 2. consumer.accept(delegate) 让业务链式调用
    // 3. 失败抛 FluentValidateException(@Deprecated)
}

// Yavi 模式:传 Yavi Validator 对象
protected <T> void validate(T entity, am.ik.yavi.core.Validator<T> validator) {
    // 1. validator.validate(entity)
    // 2. 失败 → Convert ConstraintViolations → ValidateError[]
    // 3. 抛 ValidateException
}

// Yavi 模式:只校验不抛
protected <T> ConstraintViolations validateOnly(T entity, am.ik.yavi.core.Validator<T> validator) { ... }

// 辅助:提取错误
protected List<String> extractMessages(ConstraintViolations violations);
protected List<String> extractFieldNames(ConstraintViolations violations);

ValidatorDelegate(链式代理,不是 Spring Bean)

13 个 fluent 方法,全部返回 ValidatorDelegate 支持链式:

// 常用方法示例
delegate.notBlank(name, "名称必填")
        .notNull(age, "年龄不能为 null")
        .range(age, "年龄必须在 1-150 之间")
        .regex(email, "邮箱格式不正确")
        .exist(role, "角色必须在允许列表中");

同时 extends FluentValidator,也能混用原生 FluentValidator 的链式 API。

异常

// 统一异常(推荐使用)
new ValidateException(List.of(error1, error2))
new ValidateException(String field, String message)

// 废弃异常(Fluent 路径仍在使用)
@Deprecated FluentValidateException(List<ValidationError>)

// 两个异常都持有 List<ValidateError>
ValidateException.getErrors()     // List<ValidateError> 不可变副本
ValidateException.getMessages()   // List<String>
ValidateException.getFields()     // List<String>
ValidateException.buildMessage()  // "field: msg; field: msg"

五、13 个 Validator 实现

全部 extends ValidatorHandler<T> 实现 Validator<T>,有两种使用模式:

Validator泛型检查逻辑byError 工厂(Fluent 路径)
NotNullValidatorObjectnull 检查✅
NotBlankValidatorString空字符串/纯空白(hutool StrUtil.isBlank)✅
NotEmptyValidatorCollection空集合✅
LengthValidatorString长度✅
LengthIgnoreNoneValidatorString长度(忽略 None 值)✅
RangeValidatorNumbermin/max 范围(默认 0 ~ Long.MAX_VALUE)✅
RegexValidatorString正则匹配✅
EqualToValidatorObject等于指定值✅
NotEqualValidatorObject不等于指定值✅
ExistValidatorObject值在指定 Collection 中✅
NotExistValidatorObject值不在指定 Collection 中✅
ExistKeyValidatorObjectkey 在 Map 中存在✅
NotExistKeyValidatorObjectkey 在 Map 中不存在✅

两种构造模式:

// 注解式路径(fieldName 有,SpringRegistry 自动发现)
new NotNullValidator("age")

// Fluent 路径(fieldName=null,ValidatorDelegate 手动 new)
NotNullValidator.byError("年龄不能为 null")

六、自动配置

配置类条件注册 Bean
FluentValidateConfiguration无条件fluentHibernateValidator(javax.validation.Validator)+ SpringApplicationContextRegistry
YaviValidateAutoConfiguration@ConditionalOnClass(ValidatorBuilder.class)空壳(无 Bean 产出)

spring.factories 注册两个配置类平级,无 @AutoConfigureAfter / @AutoConfigureBefore。

运行时依赖:fluentHibernateValidator Bean 创建需要 javax.validation.Validator 实现(通常 Hibernate Validator)。easy-validate 的 pom.xml 把 hibernate-validator 放在 test scope——生产环境必须由应用自行提供(spring-boot-starter-validation 会带)。


七、Fluent vs Yavi 模式差异

维度FluentValidatorYavi
API 风格命令式链式,运行时拼装规则声明式构建器,Validator 可预编译复用
注解支持有(HibernateValidator 跑 JSR303)无(纯编程式)
错误模型FluentValidateException(@Deprecated)→ 内含 ValidateErrorValidateException(推荐)→ 内含 ValidateError
自动配置无条件@ConditionalOnClass 空壳
规则定义时机运行时(每次 validate 都 new ValidatorDelegate)启动时(构建一次 Validator,复用)
适用场景条件复杂、规则动态规则固定、性能敏感

八、已知限制

限制严重度说明
FluentValidateException 仍在使用中标记 @Deprecated 但 Fluent 路径仍抛它,应统一到 ValidateException
13 个 Validator 无 @Component低SpringRegistry 自动发现机制形同虚设(但 Fluent 手动 new 不受影响)
RangeValidator.byError 默认值无意义低min=0 max=Long.MAX_VALUE,范围检查靠自定义 message
YaviValidateAutoConfiguration 是空壳低Yavi 不需要 Spring 集成,占位而已
hibernate-validator test scope中生产必须应用自行提供 JSR303 实现
规则无持久化低不与 easy-store 集成,规则改了改代码

九、关键设计决策

决策内容理由
统一门面 ValidateSupport 给业务继承abstract 基类 + protected 方法业务代码选择用哪个重载即可,零配置切换引擎
ValidatorDelegate 链式代理装饰器模式包装 FluentValidator提供 13 个领域特定 fluent 方法
双引擎都是 compile scopeFluentValidator + Yavi 硬依赖应用不需要额外引入就能用两种模式
FluentValidateConfiguration 无条件加载两个引擎平级,Fluent 是主力业务几乎都会用到 Fluent 模式
YaviValidateAutoConfiguration 是空壳Yavi 是纯 Java API,不需要 Spring Bean占位,保证 @ConditionalOnClass 保护
异常模型统一 ValidateError两个引擎转换到同一错误类型业务代码只需要处理一种异常
FluentValidateException @Deprecated未来统一抛 ValidateException渐进迁移路径