Spring Validation

本文介绍了如何在Spring Boot中利用hibernate-validator进行参数校验,包括requestBody、requestParam和PathVariable的验证,以及如何处理嵌套校验、集合校验和自定义校验。重点讲解了如何使用@Validated和自定义约束注解实现业务规则。
摘要由CSDN通过智能技术生成

引入依赖

如果 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值