JSR303校驗+統一異常處理細節+同一字段多個校驗注解的結果如何處理


JSR303

  • 1)、導入 javax.validation、hibernate-validator依賴,尤其是第二個,在springboot應用中使用校驗,必須導入
  • 2)、給Bean的字段添加校驗注解:javax.validation.constraints,並定義自己的message提示
    • @NotNull: CharSequence, Collection, Map 和 Array 對象不能是 null, 但可以是空集(size = 0)。
    • @NotEmpty: CharSequence, Collection, Map 和 Array 對象不能是 null 並且相關對象的 size 大於 0。
    • @NotBlank: String 不是 null 且 至少包含一個字符
  • 3)、開啟校驗功能 使用@Valid
    • 效果:校驗錯誤以后會有默認的響應;
  • 4)、給校驗的bean后緊跟一個BindingResult,就可以獲取到校驗的結果
  • 5)、分組校驗(多場景的復雜校驗)
    - @NotBlank(message = "品牌名必須提交",groups ={AddGroup.class,UpdateGroup.class})
    - @Validated({AddGroup.class}),給校驗注解標注什么情況需要進行校驗
    - 默認沒有指定分組的字段校驗使用注解@Valid,在分組校驗情況下,只會在@Validated({AddGroup.class})生效;
  • 6)、自定義校驗
    • 1、編寫一個自定義的校驗注解
    • 2、編寫一個自定義的校驗器 ConstraintValidator
    • 3、關聯自定義的校驗器和自定義的校驗注解
      • @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多個不同的校驗器,適配不同類型的校驗】 })
  • 統一的異常處理
    • @ControllerAdvice
    • 編寫異常處理類,使用@ControllerAdvice。
    • 使用@ExceptionHandler標注方法可以處理的異常。

    舉例

    要校驗的實體類

    • 注意 username, password, code 字段都有多個校驗注解
	@Data
	public class RegisterVO {
	
	    
	    @NotBlank(message = "用戶名不能為空")
	    @Length(min = 4, max = 20, message = "用戶名長度為4-20字符")
	    private String username;
	
	    @NotBlank(message = "密碼不能為空")
	    @Length(min = 8, max = 16, message = "密碼長度為8-16字符")
	    private String password;
	
	    @Pattern(regexp = "^1[3-9][0-9]{9}$", message = "手機號格式不合法")
	    private String phone;
	
	    @NotNull(message = "驗證碼不能為空")
	    @Pattern(regexp = "^[0-9]{6}$", message = "驗證碼為6位數字")
	    private String code;
	}

控制器

  • 注意我這里沒標@RequestBody注解,這個等會再說
	@PostMapping("/register")
    public String register(@Validated RegisterVO registerVO) {
        // 校驗出錯會被異常處理器處理
        return "success";
    }

異常處理器

  • 使用@ControllerAdvice和@ExceptionHandler組合
  • @ExceptionHandler標注在方法上,指定這個方法處理的是哪個異常
  • @ControllerAdvice指名這個類既是一個控制器,也是一個異常處理類,也就是說,你下面的方法,
    • 如果返回值是String,那么它也會被視圖解析器處理,返回視圖頁面;
    • 如果你想讓它返回json數據,那么加上@ResponseBody注解即可;
    • 如果你這個類所有方法最終都不返回視圖,只返回json,那么很簡單,直接把@
      ControllerAdvice換成@RestControllerAdvice即可。

現在我們使用下面這個異常處理來處理對前端傳來的數據RegisterVO 進行校驗的結果。當數據校驗失敗時,會拋出異常,會拋出哪個異常呢,我們先直接使用Exception.class來接收,使用它總是沒錯的。

  • 我們把所有校驗結果封裝成一個map,key是字段名字,value是校驗出錯的信息。
@ControllerAdvice
public class AuthExceptionHandler {

    /**
     * 注冊,表單提交數據格式校驗失敗;返回json數據
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler({Exception.class})
    public Map<String, String> exceptionHandler(Exception e) {
        BindingResult bindingResult = e.getBindingResult();
        // 使用stream api
        Map<String, String> map = bindingResult.getFieldErrors()
        	.stream()
        	.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        // 上面那種是種簡寫,如果你不熟悉,這樣也可以
        // Map<String, String> map = new HashMap<>();
        // bindingResult.getFieldErrors().forEach(fieldError -> {
        //     String field = fieldError.getField();
        //     String message = fieldError.getDefaultMessage();
        //     map.put(field, message);
        // });
        return map;
    }

}

驗證

然后我們讓前端提交一個全空的數據
在這里插入圖片描述
我們期待的返回給我們校驗結果,以json數據返回。但是它報錯了!!!

java.lang.IllegalStateException: Duplicate key 用戶名長度為4-20字符

DuplicateKey一般是兩個相同鍵出現,比如你在數據庫插入兩條id字段相同的記錄,假設id是唯一索引,此時就會拋出DuplicateKeyXXXException,仔細查看報錯內容,發現出錯的代碼位置

at com.vivi.auth.exception.AuthExceptionHandler.bindingExceptionHandler(AuthExceptionHandler.java:38)

也就是我們寫的異常處理的這一行

Map<String, String> map = bindingResult.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));

所以可以得出結果,肯定是這個校驗結果是,某個key出現了兩次,導致無法封裝成功,因此他也不知道同一個鍵,第二次的值是要丟掉還是替換第一個呢?

所以這兩個相同鍵是哪里來的?還記得我開始寫的 RegisterVO 類么,有些字段上面有兩個校驗注解,那么是這個原因么,我們可以在異常處理方法上debug,在它封裝成map之前,看一下它這個校驗結果里面有什么

我們發現有6個校驗錯誤,其中 username 和 password 都出現了兩次,正如我們的校驗注解縮寫,每個字段都有兩個校驗

    @NotBlank(message = "用戶名不能為空")
    @Length(min = 4, max = 20, message = "用戶名長度為4-20字符")
    private String username;

    @NotBlank(message = "密碼不能為空")
    @Length(min = 8, max = 16, message = "密碼長度為8-16字符")
    private String password;

在這里插入圖片描述
我們在點開看一下,比如 username 的兩個結果,是不是我們的校驗注解所寫的message,的確是的!
在這里插入圖片描述

總結:
  • 某個字段上有兩個或多個校驗注解時,如果兩個的規則都被觸發,那么就會有兩個鍵相同(都是這個字段名),值不同(兩個校驗各自的message)的校驗結果。
  • 這時我們想把它封裝成一個map,直接使用使用之前那種寫法肯定是不行的,我們可以簡單修改一下,既然是同一個字段的校驗結果,將這兩個信息聯合起來就好了呀,比如入下面這樣:
	bindingResult.getFieldErrors().forEach(fieldError -> {
	    String field = fieldError.getField();
	    String message = fieldError.getDefaultMessage(); // 當次結果
	    String msg = map.getOrDefault(field, ""); // 上次校驗結果
	    map.put(field, msg + "," + message); // 連接起來再賦值
	});

最后,還有一個問題,就是之前說的 數據校驗失敗拋出的異常到底是什么類型

我這里簡單說一下,有興趣的朋友可以自己翻翻源碼。

  • 如果前端是form表單提交數據,數據格式就為 'application/x-www-form-urlencoded;charset=UTF-8' ,Spring 使用 FormHttpMessageConverter 轉化請求體(表單數據),到封裝成對象 ,校驗失敗拋出異常 BindException;這種情況下,我們在controller接收時也不能使用@RequestBody,否則會報錯 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported, org.springframework.web.HttpMediaTypeNotSupportedException
  • 如果前端是ajax或別的方式,以json格式傳輸數據,那我們接收時就需要添加 @RequestBody ,Spring按照json格式進行解析以及封裝,校驗失敗拋出 MethodArgumentNotValidException
  • 最后,如果你還是不清楚,你就使用Exception來處理,肯定能成功,你再打印一下異常的類型就能看到它具體是哪個類了!


免責聲明!

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



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