SpringBoot中使用hibernate-validator进行参数校验

本文有参考这篇:https://blog.csdn.net/weixin_41146599/article/details/81908532


一、参数校验(类属性)

在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等。 会导致如下情况:
- 验证代码繁琐
- 方法内代码显得冗长
- 修改相同逻辑验证代码时需要修改涉及到同样逻辑的所有地方

spring-boot-starter-web 包里面有hibernate-validator包,不需要再引入依赖。

使用@Valid对参数进行校验在使用对象进行参数接收时,我们可以对参数进行校验录入我们可以对属性username和password加上如下注解

public class User{

    @NotBlank(message = "{user.name.notBlank}",groups = {GroupA.class})
    private String username; // 用户名

    @Max(value = 999999,message = "超过最大数值")
    @Min(value = 000000,message = "密码设定不正确")
    @NotNull(message = "密码不能为空")
    private String password; // 密码
}

然后我们给Controller层的方法中接收的对象前加入@Valid注解,并在参数中加入BindingResult来获取错误信息。 在逻辑处理中我们判断BindingResult知否含有错误信息,如果有错误信息,则直接返回错误信息。

 // 添加用户
    @PostMapping("/createuser")  // 多个 @Valid 则对应多个 BindingReslt
    public String createUser(@Valid User user, BindingResult bindingResult){
        if (bindingResult.hasErrors()){        
            return bindingResult.getFieldError().getDefaultMessage();
        }else{
            userService.createUser(userInfo.getTel(),userInfo.getPassWord(); return "OK";
        }
    }

Hibernate validator的校验只能对Object的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了++MethodValidationPostProcessor拦截器++后可以实现对方法参数的校验。

二、参数校验(方法参数)

1 实例化MethodValidationPostProcessor拦截器

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
}

2 在所要实现方法参数校验的类上面添加@Validated,如下

@Validated  // 加入注释
@RestController
@RestMapping("/validate")
public class ValidateController {
}12345

3 在方法上面添加校验规则

    @GetMapping("/paramcheck")
    public String paramCheck(@Length(min = 10) @RequestParam String name) {
        System.out.println(name);
        return null;
    }

4 当方法上面的参数校验失败,spring 框架就回抛出异常

{
  "timestamp": 1476108200558,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "javax.validation.ConstraintViolationException",
  "message": "No message available",
  "path": "/paramcheck"
}

三、hibernate的校验模式

通常的校验会验证所有属性并返回一个错误消息集合,而快速失败模式通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。
Hibernate Validator有以下两种验证模式:

  1. 普通模式(默认)
     会校验完所有的属性,然后返回所有的验证失败信息
  2. 快速失败返回模式
     只要有一个验证失败,则返回此条验证失败信息

两种验证模式配置方式:

就是在SpringBoot中配置一个Bean

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure() // true-快速失败返回模式    false-普通模式   
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
}

四、分组校验

有这样一种场景,新增用户信息的时候,不需要验证userId(因为系统生成);
修改的时候则需要验证userId,这时候可用分组验证功能。

public interface GroupA {
}

public interface GroupB {
}

public class Person {

    @NotBlank
    @Range(min = 1,max = Integer.MAX_VALUE,message = "必须大于0",groups = {GroupA.class})
    private Integer userId;// 用户id

    @NotBlank
    @Length(min = 4,max = 20,message = "必须在[4,20]",groups = {GroupB.class})
    private String userName;// 用户名

    @NotBlank
    @Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
    private Integer age;// 年龄

    @NotNull(message = "地址不能为空",groups = {GroupB.class})
    private String address;// 地址
}

如上Person所示,3个分组分别验证字段如下:
GroupA验证字段userId;
GroupB验证字段userName,address;
Default验证字段age(默认分组)

A情景
只验证GroupA或GroupB标记的分组:
(可叠加 GroupA,GroupB)

    // 添加用户
    @PostMapping("/createuser2")  // 多个@Valid对应则多个BindingReslt
    public String createuser2(@Validated(GroupA.class) User user, BindingResult bindingResult){    
    if (bindingResult.hasErrors()){        
        return bindingResult.getFieldError().getDefaultMessage();
    }else{
        userService.createUser(userInfo.getTel(),userInfo.getPassWord());   
        return "OK";   
    }
}12345678910

B情景
指定验证组的顺序:
除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证:
(GroupA>GroupB>Default)

@GroupSequence({GroupA.class, GroupB.class, Default.class})
public interface GroupOrder {
}

五、SpringBoot中配置错误信息配置文件

在resource 目录下新建提示信息配置文件“ValidationMessages.properties“

注意:名字必须为“ValidationMessages.properties“ 因为SpringBoot自动读取classpath中的ValidationMessages.properties里的错误信息

@NotBlank(message = "{user.name.notBlank}")
    private String name;

六、@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

Controller 层的代码,就不需要进行异常处理了:

主要是MethodArgumentNotValidException这个异常不用每个Controller都来一遍处理的代码了

/**
 * @Author: wei1
 * @Date: Create in 2018/9/14 19:55
 * @Description: @ControllerAdvice + @ExceptionHandler 使用 GlobalExceptionHandler 全局处理 Controller 层异常的示例
 * 参考:https://blog.csdn.net/kinginblue/article/details/70186586
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理所有不可知的异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Map<String, Object> handleException(Exception e) throws Exception {
        LOGGER.error(e.getMessage(), e);
//        由于Exception是异常的父类,如果你的项目中出现过在自定义异常中使用@ResponseStatus的情况,
//        你的初衷是碰到那个自定义异常响应对应的状态码,而这个控制器增强处理类,会首先进入,并直接
//        返回,不会再有@ResponseStatus的事情了,这里为了解决这种纰漏,我提供了一种解决方式。
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }

        Map<String, Object> result = new HashMap<>();
        result.put("Error Message: ", e.getMessage());
        return result;
    }

    /**
     * 处理所有业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(UserNotExistException.class)
    @ResponseBody
    public Map<String, Object> handleBusinessException(UserNotExistException e) {
        LOGGER.error(e.getMessage(), e);

        Map<String, Object> result = new HashMap<>();
        result.put("Error Message: ", e.getMessage());
        return result;
    }

    /**
     * 处理所有接口数据验证异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Map<String, Object> handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) {
        LOGGER.error(e.getMessage(), e);

        Map<String, Object> result = new HashMap<>();
        String messages = "";
        for (ObjectError objectError : e.getBindingResult().getAllErrors()) {
            messages += objectError.getDefaultMessage() + ",";
        }

        result.put("Error Message: ", messages);
        return result;
    }
}

七、常见的注解

JSR303中内置的注解

注解名 作用
@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(regex=,flag=) 元素必须符合指定的正则表达式

Hibernate Validator对JSR303的扩展注解

注解名 作用
@NotBlank(message =) 字符串非null,且长度必须大于0
@Email 元素必须是电子邮箱地址
@Length(min=,max=) 字符串的大小必须在指定的范围内
@NotEmpty 字符串非空
@Range(min=,max=,message=) 元素必须在合适的范围内

猜你喜欢

转载自blog.csdn.net/qq_37859539/article/details/82708993