验证失败时在 Spring RestController 中抛出什么类型的异常?
What type of exception to throw in Spring RestController when validation fails?
在 Spring RestController
中,我只需将相应的方法参数注释为 @Valid
或 @Validated
即可对 RequestBody
进行输入验证。其他一些验证只能在对传入数据进行一些处理后执行。我的问题是,我应该使用什么类型的异常,使其类似于 @Valid
注释抛出的异常,以及如何从验证结果中构造此异常。这是一个例子:
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(@RequestBody @Validated(InputChecks.class) Order order) {
// Some processing of the Order goes here
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
// What to do now with the validation errors?
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
为了处理第二个 运行 中的验证错误,我可以想到三种不同的方法。首先,您可以从 ConstraintViolation
中的 Set
中提取验证错误消息,然后 return 一个适当的 HTTP 响应,比如 400 Bad Request
,将验证错误消息作为响应正文:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
Set<String> validationMessages = violations
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toSet());
return ResponseEntity.badRequest().body(validationMessages);
}
// the happy path
这种方法适用于双重验证是少数控制器的要求的情况。否则,最好抛出一个全新的 Exception
或重用 spring 相关的异常,比如 MethodArgumentNotValidException
,并定义一个通用的 ControllerAdvice
来处理它们:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
throw new ValidationException(violations);
}
以及控制器建议:
@ControllerAdvice
public class ValidationControllerAdvice {
@ExceptionHandler(ValidationException.class)
public ResponseEntity handleValidtionErrors(ValidationException ex) {
return ResponseEntity.badRequest().body(ex.getViolations().stream()...);
}
}
您也可以抛出 spring 异常之一,例如 MethodArgumentNotValidException
。为此,您需要将 ConstraintViolation
的 Set
转换为 BindingResult
的实例,并将其传递给 MethodArgumentNotValidException
的构造函数。
对我来说,最简单的方法是使用错误对象验证对象,然后在 MethodArgumentNotValidException 中使用它。
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(@RequestBody @Validated(InputChecks.class) Order order)
throws NoSuchMethodException, SecurityException, MethodArgumentNotValidException {
// Some processing of the Order goes here
SpringValidatorAdapter v = new SpringValidatorAdapter(validator);
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(order, "order");
v.validate(order, errors, FinalChecks.class);
if (errors.hasErrors()) {
throw new MethodArgumentNotValidException(
new MethodParameter(this.getClass().getDeclaredMethod("createOrder", Order.class), 0),
errors);
}
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
这样,在第二个验证步骤中发现的错误与在@validated 参数的输入验证中发现的错误具有完全相同的结构。
在 Spring RestController
中,我只需将相应的方法参数注释为 @Valid
或 @Validated
即可对 RequestBody
进行输入验证。其他一些验证只能在对传入数据进行一些处理后执行。我的问题是,我应该使用什么类型的异常,使其类似于 @Valid
注释抛出的异常,以及如何从验证结果中构造此异常。这是一个例子:
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(@RequestBody @Validated(InputChecks.class) Order order) {
// Some processing of the Order goes here
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
// What to do now with the validation errors?
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
为了处理第二个 运行 中的验证错误,我可以想到三种不同的方法。首先,您可以从 ConstraintViolation
中的 Set
中提取验证错误消息,然后 return 一个适当的 HTTP 响应,比如 400 Bad Request
,将验证错误消息作为响应正文:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
Set<String> validationMessages = violations
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toSet());
return ResponseEntity.badRequest().body(validationMessages);
}
// the happy path
这种方法适用于双重验证是少数控制器的要求的情况。否则,最好抛出一个全新的 Exception
或重用 spring 相关的异常,比如 MethodArgumentNotValidException
,并定义一个通用的 ControllerAdvice
来处理它们:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
throw new ValidationException(violations);
}
以及控制器建议:
@ControllerAdvice
public class ValidationControllerAdvice {
@ExceptionHandler(ValidationException.class)
public ResponseEntity handleValidtionErrors(ValidationException ex) {
return ResponseEntity.badRequest().body(ex.getViolations().stream()...);
}
}
您也可以抛出 spring 异常之一,例如 MethodArgumentNotValidException
。为此,您需要将 ConstraintViolation
的 Set
转换为 BindingResult
的实例,并将其传递给 MethodArgumentNotValidException
的构造函数。
对我来说,最简单的方法是使用错误对象验证对象,然后在 MethodArgumentNotValidException 中使用它。
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(@RequestBody @Validated(InputChecks.class) Order order)
throws NoSuchMethodException, SecurityException, MethodArgumentNotValidException {
// Some processing of the Order goes here
SpringValidatorAdapter v = new SpringValidatorAdapter(validator);
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(order, "order");
v.validate(order, errors, FinalChecks.class);
if (errors.hasErrors()) {
throw new MethodArgumentNotValidException(
new MethodParameter(this.getClass().getDeclaredMethod("createOrder", Order.class), 0),
errors);
}
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
这样,在第二个验证步骤中发现的错误与在@validated 参数的输入验证中发现的错误具有完全相同的结构。