通用校验框架

通用校验框架

背景

在互联网行业中,基于Java开发的业务类系统,不管是服务端还是客户端,业务逻辑代码的更新往往是非常频繁的,这源于功能的快速迭代特性。在一般公司内部,特别是使用Java web技术构建的平台中,不管是基于模块化还是服务化的,业务逻辑都会相对复杂。

这些系统之间、系统内部往往存在大量的API接口,这些接口一般都需要对入参(输入参数的简称)做校验,以保证:

  1. 核心业务逻辑能够顺利按照预期执行。
  2. 数据能够正常存取。
  3. 数据安全性。包括符合约束以及限制,有访问权限控制以及不出现SQL注入等问题。

开发人员在维护核心业务逻辑的同时,还需要为输入做严格的校验。当输入不合法时,能够给caller一个明确的反馈,最常见的反馈就是返回封装了result的对象或者抛出exception。

目标

  • 验证逻辑与业务逻辑不再耦合
    摒弃原来不规范的验证逻辑散落的现象。

  • 校验器各司其职,好维护,可复用,可扩展
    一个校验器(Validator)只负责某个属性或者对象的校验,可以做到职责单一,易于维护,并且可复用。

  • 使用注解方式验证
    可以装饰在属性上,减少硬编码量。

  • 易扩展
    可定制化开发校验器

  • 统一的异常处理机制
    对验证过程中发生的错误、异常进行统一的处理。

关键技术

1. Guava的Preconditions类
引入

 <dependency> 
    <groupId>com.google.guava</groupId> 
    <artifactId>guava</artifactId> 
    <version>29.0-jre</version> 
 </dependency> 

使用


import com.google.common.base.Preconditions; 

public class PreconditinosDemo {
   

   public static void main(String[] args) {
   
        boolean demo=5<0;
        Preconditions.checkArgument(demo);
   }

}

输出:

 Exception in thread "main" java.lang.IllegalArgumentException 
 at com.google.common.base.Preconditions.checkArgument(Preconditions.java:128) 
 at com.guava.preconditions.PreconditinosDemo.main(PreconditinosDemo.java:15) 

  • 函数支持
函数 描述 失败异常
checkArgument(boolean) 检查boolean是否为真 IllegalArgumentException
checkNotNull(T) 检查value不为null, 直接返回value NullPointerException
checkState(boolean) 检查对象的一些状态,不依赖方法参数 IllegalStateException
checkElementIndex(int index, int size) 检查index是否为在一个长度为size的list,string或array合法的范围 IndexOutOfBoundsException
checkPositionIndex(int index, int size) 检查位置index是否为在一个长度为size的list,string或array合法的范围 IndexOutOfBoundsException
checkPositionIndexes(int start, int end, int size) 检查[start, end)是一个长度为size的list,string或array合法的范围子集 IndexOutOfBoundsException

2. Assert

函数支持

函数 描述
Assert.notNull(Object object) 判断对象非空
Assert.isTrue(Object object) 判断对象为true
Assert.notEmpty(Collection collection) 判断集合非空
Assert.hasLength(String) 字符不为null且字符长度不为0
Assert.hasText(String text) text不为null且必须至少包含一个非空的字符
Assert.isInstanceOf(Class class, Object object) object必须为class指定的类

3. Bean Validation

Bean Validation2.0是JSR第308号标准,是Java定义的一套基于注解/xml的数据校验规范。目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR380的2.0版本。
它包含两部分Bean Validation API(规范)和HibernateValidator(实现)。Bean Validation 为 JavaBean 验证定义了相应的元数据模型和API。通过使用BeanValidation 或是自己定义的 constraint,例如 @NotNull, @Max, @ZipCode ,就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraint。BeanValidation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

  • 注解支持
注解 描述
@Null 被注解的元素必须为Null
@NotNull 被注解的元素不行不为null
@AssertTrue 被注解的元素必须为true
@AssertFalse 被注解的元素必须为false
@Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最小值
@DecimalMin(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最小值
@size(max, min) 被注解的元素的大小必须在指定的范围内
@Digits(integer, fraction) 被注解的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注解的元素必须是一个过去日期
@Future 被注解的元素必须是一个将来日期
@Pattern(value) 被注解的元素必须符合指定的正则表达式

bean validation 2.0 增加了如下注解:

注解 描述
@Email 被注解的元素必须为合法邮箱
@NotEmpty 被注解的元素必须不为空
@NotBlank 被注解的字符串不为blank
@Positive 被注解的元素必须是一个数字,其值必须>0
@PositiveOrZero 被注解的元素必须是一个数字,其值必须>=0
@Negative 被注解的元素必须是一个数字,其值必须<0
@NegativeOrZero 被注解的元素必须是一个数字,其值必须<=0
@PastOrPresent 被注解的元素必须是一个过去或现在日期
@FutureOrPresent 被注解的元素必须是一个将来或现在日期

主流框架

Spring-validation

Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。Spring Boot整合JSR-303只需要添加一个starter即可,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

参数校验支持

  • 类属性校验

在DTO字段上声明约束注解:

 @Data
public class UserDTO {
   

    private Long userId;

    @NotNull
    @Length(min = 2, max = 10)
    private String userName;

    @NotNull
    @Length(min = 6, max = 20)
    private String account;

    @NotNull
    @Length(min = 6, max = 20)
    private String password;
}

在方法参数上声明校验注解:

@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
   
    // 校验通过,才会执行业务逻辑处理
    return Result.ok();
}

  • 方法参数校验
@RequestMapping("/api/user")
@RestController
@Validated
public class UserController {
   
    @GetMapping("getByAccount")
    public Result getByAccount(@Length(min = 6, max = 20) @NotNull String  account) {
   
        // 校验通过,才会执行业务逻辑处理
        return Result.ok(userDTO);
    }
}


  • 分组校验

在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能。
比如保存User的时候,UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。这个时候使用分组校验的代码示例如下:

约束注解声明适用的分组信息groups:

@Data
public class UserDTO {
   

    @Min(value = 10000000000000000L, groups = Update.class)
    private Long userId;

    @NotNull(groups = {
   Save.class, Update.class})
    @Length(min = 2, max = 10, groups = {
   Save.class, Update.class})
    private String userName;

    @NotNull(groups = {
   Save.class, Update.class})
    @Length(min = 6, max = 20, groups = {
   Save.class, Update.class})
    private String account;

    @NotNull(groups = {
   Save.class, Update.class})
    @Length(min = 6, max = 20, groups = {
   Save.class, Update.class})
    private String password;

    /**
     * 保存的时候校验分组
     */
    public interface Save {
   
    }

    /**
     * 更新的时候校验分组
     */
    public interface Update {
   
    }
}

@Validated注解上指定校验分组

@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
   
    // 校验通过,才会执行业务逻辑处理
    return Result.ok();
}

@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
   
    // 校验通过,才会执行业务逻辑处理
    return Result.ok();
}

JSR303本身的@Valid并不支持分组校验,但是Spring在其基础提供了一个注解@Validated支持分组校验。@Validated这个注解value属性指定需要校验的分组。

  • 嵌套校验

DTO类里面的字段除了基本数据类型,还有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。嵌套校验很简单,只需要在嵌套的实体属性标注@Valid注解,则其中的属性也将会得到校验,否则不会校验。

@Data
public class UserDTO {
   

    @Min(value = 10000000000000000L, groups = Update.class)
    private Long userId;

    @NotNull(groups = {
   Save.class, Update.class})
    @Length(min = 2, max = 10, groups = {
   Save.class, Update.class})
    private String userName;

    @NotNull(groups = {
   Save.class, Update.class})
    @Valid
    private Job job;

    @Data
    public static class Job {
   

        @Min(value = 1, groups = Update.class)
        private Long jobId;

        @NotNull(groups = {
   Save.class, Update.class})
        @Length(min = 2, max = 10, groups = {
   Save.class, Update
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值