(校验) 在这里插入图片描述

主页传送门:📀 传送

🍇 概述


  Spring Validation是Spring框架提供的一种数据校验方式,它可以对Bean的属性进行校验,确保属性值的正确性。在编写Java Web应用程序时,经常需要对表单提交的数据进行验证,以确保数据的合法性和正确性。使用Spring Validation可以大大简化这个过程,让开发者能够更加专注于业务逻辑的实现。

   Spring的验证特性主要基于hibernate validator实现。hibernate validator是一个Java Bean Validation的参考实现,支持Java标准规范,包括JSR-303(Java Specification Requests 303,简称JSR-303)和JSR-349(Java Specification Requests 349,简称JSR-349)

  Java API 规范(JSR303)定义了Bean校验的标准validation-api,但没有提供具体的实现方法。hibernate validation是对这个规范的实现,并增加了如@Email@Length等校验注解。(hibernate validation 是一个基于 JSR-303 规范实现的校验框架,它提供了一系列的校验注解和 API,可以帮助我们对数据进行校验。在 Spring 中,我们可以通过使用 hibernate validation 来进行数据校验。具体来说,我们可以使用 @Valid 注解来对需要校验的数据进行标记,然后使用 BindingResult 类来获取校验结果)Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数的自动校验。

🍉 使用场景


==Spring 校验使用场景==

  • Spring 常规校验(Validator)
  • Spring 数据绑定(DataBinder)
  • Spring Web 参数绑定(WebDataBinder)
  • Spring WebMVC/WebFlux 处理方法参数校验

🍉 依赖引入


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

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

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

  • POST、PUT 请求,使用 requestBody 传递参数;
  • GET 请求,使用 requestParam/PathVariable 传递参数。

  实际上,不管是 requestBody 参数校验还是方法级别的校验,最终都是调用 Hibernate Validator 执行校验,Spring Validation 只是做了一层封装。

🍍 校验示例


🍈(1)在实体上标记校验注解


示例

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

    @NotNull
    private Long id;

    @NotBlank
    @Size(min = 2, max = 10)
    private String name;

    @Min(value = 1)
    @Max(value = 100)
    private Integer age;

}

  上述类使用了Lombok库的@Data、@NoArgsConstructor和@AllArgsConstructor注解,分别用于自动生成getter、setter、无参构造方法和全参构造方法

🍒(2)在方法参数上声明校验注解


@Slf4j
@Validated
@RestController
@RequestMapping("validate1")
public class ValidatorController {

    /**
     * {@link RequestBody} 参数校验
     */
    @PostMapping(value = "save")
    public DataResult<Boolean> save(@Valid @RequestBody User entity) {
        log.info("保存一条记录:{}", JSONUtil.toJsonStr(entity));
        return DataResult.ok(true);
    }

    /**
     * {@link RequestParam} 参数校验
     */
    @GetMapping(value = "queryByName")
    public DataResult<User> queryByName(
        @RequestParam("username")
        @NotBlank
        @Size(min = 2, max = 10)
        String name
    ) {
        User user = new User(1L, name, 18);
        return DataResult.ok(user);
    }

    /**
     * {@link PathVariable} 参数校验
     */
    @GetMapping(value = "detail/{id}")
    public DataResult<User> detail(@PathVariable("id") @Min(1L) Long id) {
        User user = new User(id, "李四", 18);
        return DataResult.ok(user);
    }

}

  上述类ValidatorController,使用了Spring框架。它是一个RESTful风格的控制器,用于处理HTTP请求。这个类有三个方法:

  • save方法:使用@PostMapping注解,处理POST请求,路径为"validate1/save"。该方法接收一个User类型的参数entity,使用@Valid和@RequestBody注解进行参数校验。如果校验通过,将记录保存到数据库,并返回一个表示成功的DataResult对象。

  • queryByName方法:使用@GetMapping注解,处理GET请求,路径为"validate1/queryByName"。该方法接收一个名为username的请求参数,使用@NotBlank、@Size注解进行参数校验。如果校验通过,根据name查询用户信息,并返回一个包含用户信息的DataResult对象。

  • detail方法:使用@GetMapping注解,处理GET请求,路径为"validate1/detail/{id}"。该方法接收一个名为id的路径变量,使用@Min注解进行参数校验。如果校验通过,根据id查询用户信息,并返回一个包含用户信息的DataResult对象。

🍏 自定义校验


  虽然 hibernate validation 提供了很多常用的校验注解和 API,但是有时候我们还是需要根据具体的业务需求来自定义校验器。在 Spring 中,我们可以通过实现 Validator 接口或者使用 @InitBinder 注解来实现自定义校验器。

🍏 示例


自定义校验器 MyValidator

public class MyValidator implements Validator {  
    @Override  
    public boolean supports(Class<?> aClass) {  
        return User.class.equals(aClass);  
    }  
  
    @Override  
    public void validate(Object o, Errors errors) {  
        User user = (User) o;  
        if (user.getAge() < 18) {  
            errors.rejectValue("age", "user.age.error");  
        }  
    }  
}  
  
@InitBinder("user")  
public void initBinder(WebDataBinder binder) {  
    binder.addValidators(new MyValidator());  
}  
  
@RequestMapping("/user")  
public String addUser(@Valid User user, BindingResult result) {  
    if (result.hasErrors()) {  
        // 校验失败,处理错误  
        return "user-add-error";  
    }  
    // 校验通过,添加用户  
    return "user-add-success";  
}

特定的校验规则

public class CustomValidator implements ConstraintValidator<Custom, String> {
    @Override
    public void initialize(Custom constraintAnnotation) {}
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // do some custom validation logic here ...
        return true; // or false based on the validation result
    }
}

/** 在需要使用该规则的地方添加相应的注解即可 */

public class User {
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线", groups = {Create.class}, attributeName = "username")
    private String username;
    // other fields and getter/setter methods ...
}

🥝 抛异常


  如果请求参数不满足校验规则,则会抛出ConstraintViolationExceptionMethodArgumentNotValidException异常。

🥝 异常统一处理


  在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理所有不可知的异常
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Throwable.class)
    public Result handleException(Throwable e) {
        log.error("未知异常", e);
        return new Result(ResultStatus.HTTP_SERVER_ERROR.getCode(), e.getMessage());
    }

    /**
     * 统一处理请求参数校验异常(普通传参)
     *
     * @param e ConstraintViolationException
     * @return {@link DataResult}
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ ConstraintViolationException.class })
    public Result handleConstraintViolationException(final ConstraintViolationException e) {
        log.error("ConstraintViolationException", e);
        List<String> errors = new ArrayList<>();
        for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
            Path path = violation.getPropertyPath();
            List<String> pathArr = StrUtil.split(path.toString(), ',');
            errors.add(pathArr.get(0) + " " + violation.getMessage());
        }
        return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, ","));
    }

    /**
     * 处理参数校验异常
     *
     * @param e MethodArgumentNotValidException
     * @return {@link DataResult}
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ MethodArgumentNotValidException.class })
    private Result handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException", e);
        List<String> errors = new ArrayList<>();
        for (ObjectError error : e.getBindingResult().getAllErrors()) {
            errors.add(((FieldError) error).getField() + " " + error.getDefaultMessage());
        }
        return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, ","));
    }

}

  上述全局异常处理器类,用于处理控制器中抛出的异常。这个类有三个方法:

  • handleException方法:使用@ResponseBody、@ResponseStatus和@ExceptionHandler注解,处理所有不可知的异常。如果发生异常,记录错误日志,并返回一个表示服务器错误的Result对象。

  • handleConstraintViolationException方法:使用@ResponseBody、@ResponseStatus和@ExceptionHandler注解,统一处理请求参数校验异常(普通传参)。如果发生ConstraintViolationException异常,记录错误日志,将异常信息转换为字符串列表,并返回一个表示请求错误的Result对象。

  • handleMethodArgumentNotValidException方法:使用@ResponseBody、@ResponseStatus和@ExceptionHandler注解,处理参数校验异常。如果发生MethodArgumentNotValidException异常,记录错误日志,将异常信息转换为字符串列表,并返回一个表示请求错误的Result对象。

  如果喜欢的话,欢迎 🤞关注 👍点赞 💬评论 🤝收藏  🙌一起讨论
  你的支持就是我✍️创作的动力!					  💞💞💞