SpringBoot 異常處理/自定義格式校驗的問題實例詳解
1. 問題簡要描述
這個問題是在測試自定義注解實現(xiàn)數(shù)據(jù)格式校驗時遇到的,當前有兩個注解,ValidateParamsMatched和SafeParam。前者的功能是對類中的兩個列表的長度進行比較,后者則是對字符串進行校驗,判斷是否包含特殊字符
但是,當分別觸發(fā)這兩個校驗時,都會觸發(fā)異常并返回請求,但之后后者能返回請求信息。
這里問題本質上是沒弄懂對應的異常處理代碼,但也值得記錄一下。
2. 異常觸發(fā)
1) 參數(shù)級別約束
在 Controller 方法上直接使用參數(shù)級約束,并且,Controller 類上有 @Validated 注解時,驗證失敗會觸發(fā) ConstraintViolationException 異常
@Validated // 必需 @RestController public class ProductController { @GetMapping("/products") public Product getProduct(@ValidCategory String category) { // ... } }
在 DTO 的字段上使用參數(shù)級約束,且方法參數(shù)前有 @Valid 注解時,驗證失敗會觸發(fā) MethodArgumentNotValidException 異常
@PostMapping("/products") public ResponseEntity<?> createProduct( @Valid @RequestBody ProductRequest request // 觸發(fā)驗證 ) { // ... }
2) 類級別約束
在實體類上標注了類級約束,且Controller 方法參數(shù)使用了 @Valid,觸發(fā)MethodArgumentNotValidException
@ValidInventory // 類級別約束 public class Product { private int stockQuantity; private int minStockLevel; } @PostMapping("/products") public ResponseEntity<?> createProduct( @Valid @RequestBody Product product // 觸發(fā)驗證 ) { // ... }
注意:類級別約束應該加在需要驗證的目標類(實體類/DTO)的類聲明上,且在 Controller 方法參數(shù)添加 @Valid 和在 Service 方法參數(shù)添加 @Valid
3. 異常處理
建議通過 @RestControllerAdvice 設置全局異常處理來對此類異常進行處置。但獲取信息時,這兩類約束的方法不同。
在 MethodArgumentNotValidException 中,字段級約束產生 FieldError,類級約束產生 ObjectError(也稱為全局錯誤)
1) 字段級別約束
獲取方式為 ex.getBindingResult().getFieldErrors(),包含字段名、錯誤消息、拒絕值。
ex.getMessage() 會返回詳細錯誤信息
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException ex) { // 只獲取字段錯誤 String errorMsg = ex.getBindingResult().getFieldErrors().stream() .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) .collect(Collectors.joining("; ")); log.warn("請求值驗證異常 | request={}", ex.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMsg); }
2) 類級別約束
獲取方式為 ex.getBindingResult().getGlobalErrors(),包含對象名、錯誤消息。
ex.getMessage() 返回通用消息。
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException ex) { // 只獲取字段錯誤 String errorMsg = ex.getBindingResult().getGlobalErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining("; ")); log.warn("請求值驗證異常 | request={}", ex.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMsg); }
在類級別約束的異常處理時,ex.getBindingResult().getFieldErrors() 會返回空值;同樣的,ex.getBindingResult().getGlobalErrors()也會返回空值
因此,可以統(tǒng)一錯誤處理格式
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException ex) { // 創(chuàng)建錯誤消息列表 List<String> errorMessages = new ArrayList<>(); // 處理字段錯誤 ex.getBindingResult().getFieldErrors().forEach(fieldError -> { String msg = String.format("%s: %s", fieldError.getField(), fieldError.getDefaultMessage()); errorMessages.add(msg); }); // 處理全局錯誤 ex.getBindingResult().getGlobalErrors().forEach(globalError -> { String msg = String.format("全局錯誤: %s", globalError.getDefaultMessage()); errorMessages.add(msg); }); // 合并所有錯誤消息 String errorMsg = String.join("; ", errorMessages); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMsg); }
4. 進一步學習
值得注意的是, ex.getMessage() 給出的結果也是不一樣的,這個與代碼實現(xiàn)有關
public String getMessage() { StringBuilder sb = new StringBuilder("Validation failed for argument at index ") .append(getParameter().getParameterIndex()).append(" in method: ") .append(getParameter().getExecutable().toGenericString()); BindingResult result = getBindingResult(); if (result.getErrorCount() > 0) { sb.append(", with ").append(result.getErrorCount()).append(" error(s): "); for (ObjectError error : result.getAllErrors()) { sb.append('[').append(error).append("] "); } } return sb.toString(); }
當只有類級約束失敗時,result.getErrorCount() == 1(只有 ObjectError)
,因此,ex.getMessage() 輸出的內容不同
- 另外,在調試的過程中還遇到了一個問題,在類級別約束中,考慮到已經存在非空校驗了,因此,在類約束中再重復做校驗。但在測試過程中發(fā)現(xiàn),輸入屬性為空時,會在類約束中報錯。后來經過查驗,確定問題原因。
- 雖然對于 Bean 的校驗按照 字段->類 的順序進行,但校驗過程會收集所有違反約束的情況,而不是在遇到第一個約束失敗時就停止。也就是說類級別的約束(即放在類上的注解)的校驗器,在字段級別約束失敗時,仍然會被執(zhí)行
到此這篇關于SpringBoot 異常處理/自定義格式校驗的文章就介紹到這了,更多相關springboot異常處理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java使用POI實現(xiàn)html和word相互轉換
這篇文章主要為大家詳細介紹了java使用POI實現(xiàn)html和word的相互轉換,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12Java?nacos動態(tài)配置實現(xiàn)流程詳解
使用動態(tài)配置的原因是properties和yaml是寫到項目中的,好多時候有些配置需要修改,每次修改就要重新啟動項目,不僅增加了系統(tǒng)的不穩(wěn)定性,也大大提高了維護成本,非常麻煩,且耗費時間2022-09-09java多線程開發(fā)ScheduledExecutorService簡化方式
這篇文章主要為大家介紹了java多線程開發(fā)ScheduledExecutorService的簡化方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03springboot后端使用LocalDate接收日期的問題解決
在做Java開發(fā)時,肯定會碰到傳遞時間參數(shù)的情況,本文主要介紹了springboot后端使用LocalDate接收日期的問題解決,具有一定的參考價值,感興趣的可以了解一下2023-09-09