Spring Validation

论坛 期权论坛 脚本     
已经匿名di用户   2022-7-2 21:57   2155   0

引入依赖

如果 spring-boot 版本小于 2.3.x,spring-boot-starter-web 会自动传入 hibernate-validator 依赖。如果 spring-boot 版本大于2.3.x,则需要手动引入依赖:

<dependency>    <groupId>org.hibernate</groupId>    <artifactId>hibernate-validator</artifactId>    <version>6.0.1.Final</version></dependency>

对于 Web 服务来说,为防止非法参数对业务造成影响,在 Controller 层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:

POST、PUT 请求,使用 requestBody 传递参数;

GET 请求,使用 requestParam/PathVariable 传递参数。

requestBody 参数校验

POST、PUT 请求一般会使用 requestBody 传递参数,这种情况下,后端使用 DTO 对象进行接收。只要给 DTO 对象加上 @Validated 注解就能实现自动参数校验。如果校验失败,会抛出 MethodArgumentNotValidException 异常,Spring 默认会将其转为 400(Bad Request)请求。

DTO 表示数据传输对象(Data Transfer Object),用于服务器和客户端之间交互传输使用的。在 spring-web 项目中可以表示用于接收请求参数的Bean对象。

这种情况下,使用 @Valid 和 @Validated 都可以。

requestParam/PathVariable 参数校验

GET 请求一般会使用 requestParam/PathVariable 传参。如果参数比较多(比如超过6个),还是推荐使用 DTO 对象接收。否则,推荐将一个个参数平铺到方法入参中。在这种情况下,必须在 Controller 类上标注 @Validated 注解,并在入参上声明约束注解(如 @Min 等)。如果校验失败,会抛出 ConstraintViolationException 异常。

统一异常处理

// 当请求参数标识注解 @RequestBody
else if (e instanceof MethodArgumentNotValidException) {
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
String msg = "";
if (bindingResult.hasErrors()) {
List<ObjectError> errors = bindingResult.getAllErrors();
errors.forEach(p -> {
FieldError fieldError = (FieldError) p;
buffer.append(fieldError.getDefaultMessage()).append(",");
});
if(buffer.length() > 1) {
msg = buffer.substring(0, buffer.lastIndexOf(","));
}
}
result.setCode(ResultCode.FAIL).setMsg(msg);
}
// @Validated一般情况
else if (e instanceof BindException) {
BindingResult bindingResult = ((BindException) e).getBindingResult();
String msg = "";
if (bindingResult.hasErrors()) {
List<ObjectError> errors = bindingResult.getAllErrors();
errors.forEach(p -> {
FieldError fieldError = (FieldError) p;
buffer.append(fieldError.getDefaultMessage()).append(",");
});
if(buffer.length() > 1) {
msg = buffer.substring(0, buffer.lastIndexOf(","));
}
}
result.setCode(ResultCode.FAIL).setMsg(msg);
}
// @PathVariable 、@RequestParam 注解的参数
else if (e instanceof ConstraintViolationException) {
List<String> errors = ((ConstraintViolationException) e).getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
String msg = "";
errors.forEach(p -> {
buffer.append(p).append(",");
});
if(buffer.length() > 1) {
msg = buffer.substring(0, buffer.lastIndexOf(","));
}
result.setCode(ResultCode.FAIL).setMsg(msg);
}

嵌套校验

前面的示例中,DTO 类里面的字段都是基本数据类型和 String 类型。但是实际场景中,有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。比如,上面保存User信息的时候同时还带有 Job 信息。需要注意的是,此时 DTO 类的对应字段必须标记 @Valid 注解。

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.class})        private String jobName;
        @NotNull(groups = {Save.class, Update.class})        @Length(min = 2, max = 10, groups = {Save.class, Update.class})        private String position;    }

集合校验

如果请求体直接传递了 JSON 数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用 java.util.Collection 下的 List 或者 Set 来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数。

包装 List 类型,并声明 @Valid 注解:

public class ValidationList<E> implements List<E> {    @Delegate // @Delegate是lombok注解    @Valid // 一定要加@Valid注解    public List<E> list = new ArrayList<>();
    // 一定要记得重写toString方法    @Override    public String toString() {        return list.toString();    }}

如果校验不通过,会抛出 NotReadablePropertyException,同样可以使用统一异常进行处理。

比如,我们需要一次性保存多个 User 对象,Controller 层的方法可以这么写:

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

自定义校验

业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验来满足我们的需求。自定义 Spring Validation 非常简单,假设我们自定义加密 id(由数字或者 a-f 的字母组成,32-256 长度)校验,主要分为两步。

  • 自定义约束注解:

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})@Retention(RUNTIME)@Documented@Constraint(validatedBy = {EncryptIdValidator.class})public @interface EncryptId {    // 默认错误消息    String message() default "加密id格式错误";
    // 分组    Class<?>[] groups() default {};
    // 负载    Class<? extends Payload>[] payload() default {};}
  • 实现 ConstraintValidator 接口编写约束校验器:

public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> {    private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");
    @Override    public boolean isValid(String value, ConstraintValidatorContext context) {        // 不为null才进行校验        if (value != null) {            Matcher matcher = PATTERN.matcher(value);            return matcher.find();        }        return true;    }}

这样我们就可以使用 @EncryptId 进行参数校验了!

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:81
帖子:4969
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP