java参数校验validation-api

一、参数校验的由来

​ 校验参数在项目中是很常见的,在java中,几乎每个有入参的方法,在执行下一步操作之前,都要验证参数的合法性,比如是入参否为空,数据格式是否正确等等,往常的写法就是一大推的if-else,既不美观也不优雅,这个时候JCP组织站出来了,并且制定了一个标准来规范校验的操作,这个标准就是Java Validation API(JSR 303)

但是这个仅仅是一个标准而是,并没有具体的实现,下面介绍两种常用实现。

二、Java Validation API 的实现者

2.1、hibernate-validator

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

这个实现是有hibernate实现的,如果入参是一个对象,配合@Valid注解即可,但是无法对单个参数应用@NotNull@Min这类的注解

2.2、spring-boot-starter-validation

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

或者

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

​ 这两个pomspringhibernate-validator的一个封装和扩展,同时提供了一个@Validated的注解,该注解即可标记在类上,也可以跟@Valid注解用法一样,标记一个入参对象,更强大的是,该注解支持了单个参数的校验,但是需要在上加@Validated这个注解,可见该注解完全可以替代@Valid注解来使用。

​ 从依赖依赖关系图,可以看出starter-webstarter-validation都依赖于hibernate-validatorhibernate-validator依赖于validation-api,而且项目中经用到的@NotBlank、@NotNull、@Min、@Valid 等注解都出自validation-api包中,hibernate-validator 中的注解已不推荐使用,validation-api的包路径为 javax.validation.constraints

image-20200825001442768

三、使用

3.1、需求

用户注册接口,名称,年龄,邮箱、不能为空

用户修改接口,名称,年龄,邮箱,主键id,不能为空

用户信息接口,入参为单个参数

3.2、代码实现

注册接口group

/**
 * 添加时的验证规则
 * @author DUCHONG
 * @since 2020-08-24 23:35:46
 */
public interface ValidAddRules {
}

修改接口group

/**
 * 修改时的验证规则
 * @author DUCHONG
 * @since 2020-08-24 23:35:46
 **/
public interface ValidUpdateRules {
}

入参对象UserRequest

/**
 * 入参对象
 *
 * @author DUCHONG
 * @since 2020-08-24 23:33
 **/
@Data
@Builder
public class UserRequest implements java.io.Serializable {

    private static final long serialVersionUID = -2655536314774756670L;
    /**
     * 主键
     */
    @NotNull(message = "id不能为空",groups = {ValidUpdateRules.class})
    @Min(1)
    private Long userId;
    /**
     * 年龄
     */
    @NotNull(message = "年龄不能为空",groups = {ValidUpdateRules.class,ValidAddRules.class})
    @Min(1)
    private Integer age;

    /**
     * 企业类型名称
     */
    @NotBlank(message = "名称不能为空",groups = {ValidUpdateRules.class,ValidAddRules.class})
    private String name;

    /**
     * 邮箱
     */
    @NotBlank(message = "邮箱不能为空",groups = {ValidUpdateRules.class,ValidAddRules.class})
    @Email(message = "邮箱格式不正确")
    private String email;

    /**
     * 昵称
     */
    private String nickName;

}

controller

import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户控制器
 *
 * @author DUCHONG
 * @since 2020-08-24 23:41
 **/
@RestController
@Slf4j
@Validated
public class UserController {

    /**
     * 用户注册
     * @param userRequest
     * @param bindingResult
     * @return
     */
    @PostMapping("/user/register")
    public String registerUser(@Validated(ValidAddRules.class) @RequestBody UserRequest userRequest, BindingResult bindingResult){

        List<String> list=new ArrayList<>();
        bindingResult.getFieldErrors().forEach(fieldError -> {
            list.add(fieldError.getDefaultMessage());
        });
        return list.stream().collect(Collectors.joining(",")) ;
    }

    /**
     * 用户注册
     * @param userId
     * @param bindingResult
     * @return
     */
    @PostMapping("/user/get")
    public String getUser(@NotNull(message = "用户id不能为空") @Min(1) Long userId, BindingResult bindingResult){


        List<String> list=new ArrayList<>();
        bindingResult.getFieldErrors().forEach(fieldError -> {
            list.add(fieldError.getDefaultMessage());
        });
        return list.stream().collect(Collectors.joining(",")) ;
    }

    /**
     * 用户修改
     * @param userRequest
     * @param bindingResult
     * @return
     */
    @PostMapping("/user/update")
    public String updateUser(@Validated(ValidUpdateRules.class) @RequestBody UserRequest userRequest, BindingResult bindingResult){

        List<String> list=new ArrayList<>();
        bindingResult.getFieldErrors().forEach(fieldError -> {
            list.add(fieldError.getDefaultMessage());
        });
        return list.stream().collect(Collectors.joining(",")) ;
    }
}

搞定!!!

​ 但是你有木有发现,每个方法上有一个BindingResult,存储这报错的信息,那是不是可以提供一个公用的方法,当校验规则触发时,能捕获到异常信息,然后返回,它来了,它就是统一的异常处理入口,话不多少上代码

四、校验统一异常处理

import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 统一的异常处理器
 * @author DUCHONG
 * @since 2020-08-25 00:57:40
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 参数合法性校验异常
     * @param exception
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException exception){

        BaseResponse exceptionInfo = getErrorInfo(exception);
        log.error("参数校验异常---{}",exceptionInfo.getMsg());
        return exceptionInfo;

    }

    /**
     * 参数合法性校验异常-类型不匹配
     * @param exception
     * @return
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public BaseResponse handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception){

        BaseResponse exceptionInfo = getErrorInfo(exception);
        log.error("参数校验异常---{}",exceptionInfo.getMsg());
        return exceptionInfo;

    }
    /**
     * 参数绑定异常
     * @param exception
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    public BaseResponse handleBindException(BindException exception) {


        BaseResponse exceptionInfo = getErrorInfo(exception);
        log.error("参数校验异常---{}",exceptionInfo.getMsg());
        return exceptionInfo;
    }

    /**
     * 违反约束异常 单个参数使用
     * @param exception
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public BaseResponse handleConstraintViolationException(ConstraintViolationException exception) {

        BaseResponse exceptionInfo = getErrorInfo(exception);
        log.error("参数校验异常---{}", exceptionInfo.getMsg());
        return exceptionInfo;
    }

    /**
     * 将List结果转换成json格式
     * @param exception
     * @return
     */
    public  BaseResponse getErrorInfo(Exception exception) {

        if(exception instanceof BindException){
            return convertBindingResultToJson(((BindException) exception).getBindingResult());
        }
        if(exception instanceof MethodArgumentNotValidException){
            return convertBindingResultToJson(((MethodArgumentNotValidException) exception).getBindingResult());
        }
        if(exception instanceof ConstraintViolationException){
            return convertSetToJson(((ConstraintViolationException) exception).getConstraintViolations());
        }
        if(exception instanceof MethodArgumentTypeMismatchException){
            String msg= exception.getMessage();
            return new BaseResponse(500, msg, null);
        }
        //未定义的异常
        return new BaseResponse(500, "未知错误", null);

    }

    /**
     * 将单个参数实体校验结果封装
     * @param constraintViolations
     * @return
     */
    public  BaseResponse convertSetToJson(Set<? extends ConstraintViolation> constraintViolations) {
        List<String> list=new ArrayList<>();
        for (ConstraintViolation violation : constraintViolations) {
            list.add(violation.getMessage());
        }
        return new BaseResponse(500,list.stream().collect(Collectors.joining(",")), null);
    }

    /**
     * 将实体对象的校验结果封装
     * @param result
     * @return
     */
    public BaseResponse convertBindingResultToJson(BindingResult result){

        List<String> list=new ArrayList<>();
        result.getFieldErrors().forEach(fieldError -> {
            list.add(fieldError.getDefaultMessage());
        });

        return new BaseResponse(500,list.stream().collect(Collectors.joining(",")),null);
    }
}

到此可以跟BindingResult说拜拜了。

posted @ 2020-08-25 01:08  npe0  阅读(4024)  评论(0编辑  收藏  举报