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 中,字段級約束產(chǎn)生 FieldError,類級約束產(chǎn)生 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() 輸出的內(nèi)容不同
- 另外,在調試的過程中還遇到了一個問題,在類級別約束中,考慮到已經(jīng)存在非空校驗了,因此,在類約束中再重復做校驗。但在測試過程中發(fā)現(xiàn),輸入屬性為空時,會在類約束中報錯。后來經(jīng)過查驗,確定問題原因。
- 雖然對于 Bean 的校驗按照 字段->類 的順序進行,但校驗過程會收集所有違反約束的情況,而不是在遇到第一個約束失敗時就停止。也就是說類級別的約束(即放在類上的注解)的校驗器,在字段級別約束失敗時,仍然會被執(zhí)行
到此這篇關于SpringBoot 異常處理/自定義格式校驗的文章就介紹到這了,更多相關springboot異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot接收數(shù)組參數(shù)和集合參數(shù)方式
這篇文章主要介紹了SpringBoot接收數(shù)組參數(shù)和集合參數(shù)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03
深入解析反編譯字節(jié)碼文件中的代碼邏輯JVM中的String操作
這篇文章主要介紹了深入解析反編譯字節(jié)碼文件中的代碼邏輯JVM中的String操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
解決SpringBoot項目中l(wèi)og4j與logback的Jar包沖突問題
這篇文章主要給大家介紹了解決SpringBoot項目中l(wèi)og4j與logback的Jar包沖突問題,文中有詳細的解決方法和沖突的原因,有遇到相同問題的朋友可以參考閱讀本文2023-10-10
springboot2如何禁用自帶tomcat的session功能
這篇文章主要介紹了springboot2如何禁用自帶tomcat的session功能,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11

