Fluent-Validator 业务校验器
背景
在互联网行业中,基于Java开发的业务类系统,不管是服务端还是客户端,业务逻辑代码的更新往往是非常频繁的,这源于功能的快速迭代特性。在一般公司内部,特别是使用Java web技术构建的平台中,不管是基于模块化还是服务化的,业务逻辑都会相对复杂。 这些系统之间、系统内部往往存在大量的API接口,这些接口一般都需要对入参(输入参数的简称)做校验,以保证: 1) 核心业务逻辑能够顺利按照预期执行。 2) 数据能够正常存取。 3) 数据安全性。包括符合约束以及限制,有访问权限控制以及不出现SQL注入等问题。 开发人员在维护核心业务逻辑的同时,还需要为输入做严格的校验。当输入不合法时,能够给caller一个明确的反馈,最常见的反馈就是返回封装了result的对象或者抛出exception。 一些常见的验证代码片段如下所示:
public Response execute(Request request) {
if (request == null) {
throw BizException();
}
List cars = request.getCars();
if (CollectionUtils.isEmpty(cars)) {
throw BizException();
}
for (Car car : cars) {
if (car.getSeatCount() < 2) {
throw BizException();
}
}
// do core business logic
}
我们可以发现,它不够优雅而且违反一些范式: 1)违反单一职责原则(Single responsibility)。核心业务逻辑(core business logic)和验证逻辑(validation logic)耦合在一个类中。 2)开闭原则(Open/closed)。我们应该对扩展开放,对修改封闭,验证逻辑不好扩展,而且一旦需要修改需要动整体这个类。 3)DRY原则(Don’t repeat yourself)。代码冗余,相同逻辑可能散落多处,长此以往不好收殓。
1.简介
FluentValidato是一个适用于以Java语言开发的程序,让开发人员回归focus到业务逻辑上,使用流式(Fluent Interface)调用风格让验证跑起来很优雅,同时验证器(Validator)可以做到开闭原则,实现最大程度的复用的工具库。
2.特点
1) 验证逻辑与业务逻辑不再耦合 摒弃原来不规范的验证逻辑散落的现象。 2) 校验器各司其职,好维护,可复用,可扩展 一个校验器(Validator)只负责某个属性或者对象的校验,可以做到职责单一,易于维护,并且可复用。 3) 流式风格(Fluent Interface)调用 4) 使用注解方式验证 可以装饰在属性上,减少硬编码量。 5) 支持JSR 303 – Bean Validation标准 或许你已经使用了Hibernate Validator,不用抛弃它,FluentValidator可以站在巨人的肩膀上。 6) Spring良好集成 校验器可以由Spring IoC容器托管。校验入参可以直接使用注解,配置好拦截器,核心业务逻辑完全没有验证逻辑的影子,干净利落。 7) 回调给予你充分的自由度 验证过程中发生的错误、异常,验证结果的返回,开发人员都可以定制。
3.上手
3.1引入maven依赖:
<dependency>
<groupId>com.baidu.unbiz</groupId>
<artifactId>fluent-validator</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
<version>1.0.5</version>
</dependency>
3.2 业务领域模型
从广义角度来说DTO(Data Transfer Object)、VO(Value Object)、BO(Business Object)、POJO等都可以看做是业务表达模型。 创建一个学生类,包含 name(姓名)、age(年龄)、schoolName(学校名称)、(area)地区
package com.example.fluentvalidator;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author :jianyul
* @date : 2022/5/16 18:00
*/
@Data
@AllArgsConstructor
public class StudentDto {
private String name;
private Integer age;
private String schoolName;
private String area;
}
3.3 Validator样例
针对schoolName(学校名称)创建一个Validator,代码如下:
public class SchoolNameValidator extends ValidatorHandler<String> implements Validator<String> {
@Override
public boolean validate(ValidatorContext context, String schoolName) {
if (!"无锡中学".equals(schoolName)) {
context.addErrorMsg("学校名称不正确");
return false;
}
return true;
}
}
很简单,实现Validator接口,泛型T规范这个校验器待验证的对象的类型,继承ValidatorHandler可以避免实现一些默认的方法,validate()方法第一个参数是整个校验过程的上下文,第二个参数是待验证对象,也就是学校名称。 验证逻辑:假设学校名称必须是无锡中学,否则通过context放入错误消息并且返回false,成功返回true。
3.4 验证
StudentDto studentDto = new StudentDto("张三", 18, "苏州中学", "无锡");
Result result =
FluentValidator.checkAll()
.on(studentDto.getSchoolName(), new SchoolNameValidator())
.doValidate()
.result(toSimple());
System.out.println(result);
//打印结果:Result{isSuccess=false, errors=[学校名称不正确]}
首先我们通过FluentValidator