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說拜拜了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM