該賞金過期5天。此問題的答案有資格獲得 50聲望獎勵。 Roddy of the Frozen Peas希望引起更多人對這個問題的關注。
我有一個 Spring @RestController,它有一個 POST 端點,定義如下:
@RestController
@Validated
@RequestMapping("/example")
public class Controller {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<?> create(@Valid @RequestBody Request request,
BindingResult _unused, // DO NOT DELETE
UriComponentsBuilder uriBuilder) {
// ...
}
}
它還有一個例外處理程式javax.validation.ConstraintViolationException:
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
ProblemDetails handleValidationError(ConstraintViolationException e) {...}
我們的 Spring-Boot 應用程式spring-boot-starter-validation用于驗證。該Request物件使用javax.validation.*注釋將約束應用于各種欄位,如下所示:
public class Request {
private Long id;
@Size(max = 64, message = "name length cannot exceed 64 characters")
private String name;
// ...
}
如上所述,如果您使用無效請求 POST 請求,驗證將拋出 ConstraintViolationException,該例外將由例外處理程式處理。這有效,我們有單元測驗,一切都很好。
我注意到BindingResult在 post 方法中沒有使用(名稱_unused和注釋//DO NOT DELETE有點危險。)我繼續洗掉了引數。突然間,我的測驗失敗了——入站請求仍然經過驗證,但它不再拋出 ConstraintValidationException ......現在它拋出一個MethodArgumentNotValidException! 不幸的是,我不能使用這個其他例外,因為它不包含我需要的格式的失敗驗證(也不包含我需要的所有資料)。
為什么BindingResult引數串列中的存在控制拋出哪個例外?ConstraintViolationException當 javax.validation 確定請求正文無效時,如何洗掉未使用的變數并仍然拋出?
彈簧啟動 2.5.5
- spring-boot-starter-web
- spring-boot-starter-validation
17. OpenJDK
uj5u.com熱心網友回復:
這里涉及兩層驗證,它們按以下順序發生:
控制器層:
- 當控制器方法的引數用
@RequestBodyor@ModelAttribute和用@Validor@Validated或名稱以“Valid”開頭的任何注釋進行注釋時啟用(請參閱此邏輯)。 - 基于
DataBinder東西 - 只能驗證請求
- 如果出現驗證錯誤并且
BindingResult控制器方法中沒有引數,則拋出org.springframework.web.bind.MethodArgumentNotValidException。否則,繼續使用BindingResult捕獲驗證錯誤資訊的引數呼叫控制器方法。
- 當控制器方法的引數用
Bean 的方法層:
- 啟用的彈簧豆如果標注有
@Validated和方法引數或回傳的值與豆驗證注釋,比如只注釋@Valid,@Size等等。 - 基于 AOP 的東西。方法攔截器是
MethodValidationInterceptor - 可以驗證請求和回應
- 在驗證錯誤的情況下,拋出
javax.validation.ConstraintViolationException。
- 啟用的彈簧豆如果標注有
最后兩層中的驗證將委托給 bean 驗證來執行實際驗證。
因為控制器實際上是一個 spring bean,所以在呼叫控制器方法時,兩層中的驗證都可以生效,這在您的案例中完全展示了,發生以下事情:
DataBinder驗證請求不正確,但由于控制器方法有BindingResult引數,它跳過拋出MethodArgumentNotValidException并繼續呼叫控制器方法MethodValidationInterceptor驗證請求不正確,并拋出ConstraintViolationException
檔案沒有明確提到這種行為。我是在閱讀源代碼后做出上述總結的。我同意這會令人困惑,尤其是在您的情況下,當驗證在兩個層和BindingResult引數中都啟用時。您可以看到 bean 驗證實際上對請求進行了兩次驗證,這聽起來很尷尬......
因此,要解決您的問題,您可以禁用控制器層中的驗證DataBinder并始終依賴于 bean 方法級別的驗證。您可以通過@ControllerAdvice使用以下@InitBinder方法創建來實作:
@ControllerAdvice
public class InitBinderControllerAdvice {
@InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(null);
}
}
那么即使是BindingResult從控制器中取出方法,也應該扔掉ConstraintViolationException。
uj5u.com熱心網友回復:
從規格:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/MethodArgumentNotValidException.html
@Valid @RequestBody Request request
如果您的物件無效,我們總是會收到 MethodArgumentNotValidException。這里的區別取決于 BindingResult ...
如果沒有 BindingResult,則會按預期拋出 MethodArgumentNotValidException。
使用 BindingResult,錯誤將被插入到 BindingResult 中。我們經常需要檢查 bindresult 是否有錯誤并對其進行處理。
if (bindingResult.hasErrors()) {
// handle error or create bad request status
}
BindingResult:“表示系結結果的通用介面。擴展錯誤注冊功能的介面,允許應用驗證器,并添加特定于系結的分析和模型構建。”
您可以再次仔細檢查系結結果中的錯誤。我沒有看到完整的代碼,所以我不知道哪個是 ConstraintViolationException 的原因,但我猜你跳過系結結果中的錯誤并繼續將物體插入資料庫并違反一些約束......
uj5u.com熱心網友回復:
我不知道BindingResultin 控制器方法的存在可以修改拋出的例外型別,因為我之前從未將它作為引數添加到控制器方法中。我通常看到的是MethodArgumentNotValidException請求正文驗證失敗的ConstraintViolationException拋出和請求引數、路徑變數和標頭值違規的拋出。中的錯誤詳細資訊的格式MethodArgumentNotValidException可能與 中的不同ConstraintViolationException,但它通常包含您需要的有關錯誤的所有資訊。下面是我為您的控制器撰寫的例外處理程式類:
package com.example.demo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ControllerExceptionHandler {
public static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);
@ExceptionHandler({ ConstraintViolationException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleValidationError(ConstraintViolationException exception) {
LOGGER.warn("ConstraintViolationException thrown", exception);
Map<String, Object> response = new HashMap<>();
List<Map<String, String>> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
Map<String, String> transformedError = new HashMap<>();
String fieldName = violation.getPropertyPath().toString();
transformedError.put("field", fieldName.substring(fieldName.lastIndexOf('.') 1));
transformedError.put("error", violation.getMessage());
errors.add(transformedError);
}
response.put("errors", errors);
return response;
}
@ExceptionHandler({ MethodArgumentNotValidException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleValidationError(MethodArgumentNotValidException exception) {
LOGGER.warn("MethodArgumentNotValidException thrown", exception);
Map<String, Object> response = new HashMap<>();
if (exception.hasFieldErrors()) {
List<Map<String, String>> errors = new ArrayList<>();
for (FieldError error : exception.getFieldErrors()) {
Map<String, String> transformedError = new HashMap<>();
transformedError.put("field", error.getField());
transformedError.put("error", error.getDefaultMessage());
errors.add(transformedError);
}
response.put("errors", errors);
}
return response;
}
}
它將MethodArgumentNotValidException和ConstraintViolationException轉換為以下相同的錯誤回應 JSON:
{
"errors": [
{
"field": "name",
"error": "name length cannot exceed 64 characters"
}
]
}
與 aMethodArgumentNotValidException相比,您在 a 中缺少哪些資訊ConstraintViolationException?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/335597.html
上一篇:從服務類呼叫方法時,SpringBoot@AutowiredNullPointerException
下一篇:MVC中的正確結構與spring
