easy-validate 校验组件设计
本文档描述 easy-validate 组件的设计与实现。该组件将两个独立的校验引擎(FluentValidator 链式 API + Yavi 声明式 API)包在统一门面基类
ValidateSupport下,通过同一套错误模型(ValidateError/ValidateException)暴露给业务代码。提供 13 个 Validator 实现(NotNull / NotBlank / Range / Regex / Exist / Length / EqualTo 等)覆盖常见业务场景。
- 一、背景与问题
- 二、组件定位与边界
- 三、总体架构
- 四、核心 API 草图
- 五、13 个 Validator 实现
- 六、自动配置
- 七、Fluent vs Yavi 模式差异
- 八、已知限制
- 九、关键设计决策
一、背景与问题
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 实现),否则
fluentHibernateValidatorBean 创建失败 - 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 路径) |
|---|---|---|---|
| NotNullValidator | Object | null 检查 | ✅ |
| NotBlankValidator | String | 空字符串/纯空白(hutool StrUtil.isBlank) | ✅ |
| NotEmptyValidator | Collection | 空集合 | ✅ |
| LengthValidator | String | 长度 | ✅ |
| LengthIgnoreNoneValidator | String | 长度(忽略 None 值) | ✅ |
| RangeValidator | Number | min/max 范围(默认 0 ~ Long.MAX_VALUE) | ✅ |
| RegexValidator | String | 正则匹配 | ✅ |
| EqualToValidator | Object | 等于指定值 | ✅ |
| NotEqualValidator | Object | 不等于指定值 | ✅ |
| ExistValidator | Object | 值在指定 Collection 中 | ✅ |
| NotExistValidator | Object | 值不在指定 Collection 中 | ✅ |
| ExistKeyValidator | Object | key 在 Map 中存在 | ✅ |
| NotExistKeyValidator | Object | key 在 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 模式差异
| 维度 | FluentValidator | Yavi |
|---|---|---|
| API 风格 | 命令式链式,运行时拼装规则 | 声明式构建器,Validator 可预编译复用 |
| 注解支持 | 有(HibernateValidator 跑 JSR303) | 无(纯编程式) |
| 错误模型 | FluentValidateException(@Deprecated)→ 内含 ValidateError | ValidateException(推荐)→ 内含 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 scope | FluentValidator + Yavi 硬依赖 | 应用不需要额外引入就能用两种模式 |
| FluentValidateConfiguration 无条件加载 | 两个引擎平级,Fluent 是主力 | 业务几乎都会用到 Fluent 模式 |
| YaviValidateAutoConfiguration 是空壳 | Yavi 是纯 Java API,不需要 Spring Bean | 占位,保证 @ConditionalOnClass 保护 |
| 异常模型统一 ValidateError | 两个引擎转换到同一错误类型 | 业务代码只需要处理一种异常 |
| FluentValidateException @Deprecated | 未来统一抛 ValidateException | 渐进迁移路径 |