JSR 303 进行后台数据校验

一、JSR 303

1、什么是 JSR 303?

  JSR 是 Java Specification Requests 的缩写,即 Java 规范提案。
  存在各种各样的 JSR,简单的理解为 JSR 是一种 Java 标准。
  JSR 303 就是数据检验的一个标准(Bean Validation (JSR 303))。
参考:
  https://www.jianshu.com/p/554533f88370

2、为什么使用 JSR 303?

  处理一段业务逻辑,首先要确保数据输入的正确性,所以需要先对数据进行检查,保证数据在语义上的正确性,再根据数据进行下一步的处理。
  前端可以通过 js 程序校验数据是否合法,后端同样也需要进行校验。而后端最简单的实现就是直接在业务方法中对数据进行处理,但是不同的业务方法可能会出现同样的校验操作,这样就出现了数据的冗余。为了解决这个情况,JSR 303 出现了。
  JSR 303 使用 Bean Validation,即在 Bean 上添加相应的注解,去实现数据校验。这样在执行业务方法前,都会根据注解对数据进行校验,从而减少自定义的校验逻辑,减少代码冗余。

3、JSR 303 常见操作?

(1)可以通过简单的注解校验 Bean 属性,比如 @NotNull、@Null 等。
(2)可以通过 Group 分组自定义需要执行校验的属性。
(3)可以自定义注解并指定校验规则。
(4)支持基于 JSR 303 的实现,比如 Hibernate Validator(额外添加一些注解)。

 

二、演示 JSR303 的简单使用

1、构建一个 SpringBoot 项目用来演示

(1)构建一个 SpringBoot 项目,以及使用 EasyCode 逆向生成相关的代码。
参考地址:
  https://www.cnblogs.com/l-y-h/p/12781586.html
模板代码地址:
  https://gitee.com/lyh-man/fast-template.git

(2)工具使用详情:
  SpringBoot 2.2.6 + JDK 1.8 + mysql 1.8 搭建基本开发环境
  IDEA + EasyCode + Lombok 插件 逆向生成基本代码
  Postman 发送请求,测试接口

2、未使用 JSR303 相关注解时

  没用 JSR 303 相关注解时,需要手动在业务方法里写处理数据的逻辑。
  修改 Controller ,简单测试一下未使用 JSR 303 相关注解时的做法。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@RequestBody Emp emp) {
        if (emp.getId() == null || emp.getName() == null) {
            return Result.error().message("数据不存在");
        }
        return Result.ok().data("items", emp).message("数据插入成功");
    }
}

《JSR 303 进行后台数据校验》

 

 

 

使用 postman 测试该接口,当 id 不存在时,会被检测到。

《JSR 303 进行后台数据校验》

 

 

 

id,name 都存在时,不会被捕获。

《JSR 303 进行后台数据校验》

 

 

 

  这里只是简单的测试一下逻辑,真实的数据检测肯定比这复杂的多,然后每个方法都需要写不同的数据处理逻辑,这样就会造成数据的冗余。而使用 JSR303 的相关注解,就很简单,继续往下看。

 

3、使用 JSR 303 相关注解处理逻辑

(1)使用步骤:
Step1:
  在相关的 Bean 上标注需要处理的注解,并指定需要提示的信息(若不指定,会从默认配置文件中读取默认的信息)。

Step2:
  在相关的方法上,使用 @Valid 注解(或者 @Validated 指定组名)标记需要被校验的数据,否则会不生效。
注意:
  检测到数据异常后,系统会向外抛出异常,如果做了统一异常处理,可以根据 postman 测试的结果,找到控制台打印出的 相应的异常,并处理。

Step3:
  处理异常。使用 BindingResult 可以获取到检测结果,然后进行处理。
  也可以使用 全局统一异常 处理(@RestControllerAdvice 与 @ExceptionHandler),处理检测结果。
注:
  统一异常处理参考:https://www.cnblogs.com/l-y-h/p/12781586.html#_label2

 

(2)使用:
Step1:
  在相关的 Bean 上标注注解,并写上指定信息。

import lombok.Data;

import javax.validation.constraints.NotNull;
import java.io.Serializable;

@Data
public class Emp implements Serializable {
    private static final long serialVersionUID = 281903912367009575L;

    @NotNull(message = "id 不能为 null")
    private Integer id;

    @NotNull(message = "name 不能为 null")
    private String name;
    
    private Double salary;
    
    private Integer age;
    
    private String email;
}

《JSR 303 进行后台数据校验》

 

 

Step2:
  修改 Controller 方法,使用 @Valid 注解标记需要检测的数据。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@Valid @RequestBody Emp emp) {
        return Result.ok().data("items", emp).message("数据插入成功");
    }
}

《JSR 303 进行后台数据校验》

 

 

Step3:
  使用 postman 测试一下。会抛出 MethodArgumentNotValidException 异常。

《JSR 303 进行后台数据校验》

 

 

控制台打印的信息:

《JSR 303 进行后台数据校验》

 

 

Step4:
  可以使用 BindingResult 去处理捕获到的数据并进行相关处理。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@Valid @RequestBody Emp emp, BindingResult result) {
        if (result.hasErrors()) {
            Map<String, String> map = new HashMap<>();
            // 获取校验结果,遍历获取捕获到的每个校验结果
            result.getFieldErrors().forEach(item ->{
                // 获取校验的信息
                String message = item.getDefaultMessage();
                String field = item.getField();
                // 存储得到的校验结果
                map.put(field, message);
            });
            return Result.error().message("数据不合法").data("items", map);
        }
        return Result.ok().data("items", emp).message("数据插入成功");
    }
}

《JSR 303 进行后台数据校验》

 

 

使用 Postman 测试。

《JSR 303 进行后台数据校验》

 

 

Step5:
  通过上面的步骤,已经可以捕获异常、处理异常,但是每次都是在业务方法中手动处理逻辑,这样的实现,代码肯定会冗余。可以将其抽出,使用 统一异常处理,每次异常发生时,将其捕获。
  根据 Step3 可以看到会抛出 MethodArgumentNotValidException 异常,所以需要将其捕获。
  需要使用 @RestControllerAdvice 与 @ExceptionHandler。

@RestControllerAdvice
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handlerValidException(MethodArgumentNotValidException e) {
        logger.error(e.getMessage(), e);
        BindingResult result = e.getBindingResult();
        Map<String, String> map = new HashMap<>();
        // 获取校验结果,遍历获取捕获到的每个校验结果
        result.getFieldErrors().forEach(item ->{
            // 存储得到的校验结果
            map.put(item.getField(), item.getDefaultMessage());
        });
        return Result.error().message("数据校验不合法").data("items", map);
    }
}

《JSR 303 进行后台数据校验》

 

 

相应的业务方法里,不需要再用 BindingResult 去处理数据了(即 Step2 的状态)。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@Valid @RequestBody Emp emp) {
        return Result.ok().data("items", emp).message("数据插入成功");
    }
}

《JSR 303 进行后台数据校验》

 

 

使用 Postman 测试。

《JSR 303 进行后台数据校验》

 

 

4、JSR 303 分组校验

(1)为什么使用 分组校验?
  通过上面的过程,可以了解到单个方法的校验规则。
  如果出现多个方法,都需要校验 Bean,且校验规则不同的时候,怎么办呢?
  分组校验就可以去解决该问题,每个分组指定不同的校验规则,不同的方法执行不同的分组,就可以得到不同的校验结果。

(2)基本认识
  JSR 303 的每个注解都默认具备三个属性:
    message 用来定义数据校验失败后的提示消息,默认读取配置文件的内容。
      全局搜索 ValidationMessages.properties,可以看到默认的信息。

    groups 用来定义分组,其是一个 class 数组,可以指定多个分组。

String message() default "{javax.validation.constraints.NotNull.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };

 

(3)使用分组步骤:
Step1:
  定义一个空接口,用于指定分组,内部不需要任何实现。

Step2:
  指定 注解时,通过 groups 指定分组。用于指定在某个分组条件下,才去执行校验规则。

Step3:
  在相关的业务方法上,通过 @Validated 注解指定分组,去指定校验。
注:
  使用分组校验后,Bean 注解上若不指定分组,则不会执行校验规则。

(4)使用:
Step1:
  创建分组接口。
  创建两个分组接口 AddGroup、UpdateGroup。
其中:
  AddGroup 用于指定 添加数据 时的校验规则(比如:id、name 均不为 null)。
  UpdateGroup 用于指定 修改数据 时的校验规则(比如:name 不允许为 null)。

《JSR 303 进行后台数据校验》

 

 

Step2:
  给 Bean 添加注解,并指定分组信息。

@Data
public class Emp
点赞