如何将 ConstraintViolationException 500 错误转换为 400 错误请求?
How to convert ConstraintViolationException 500 error to 400 bad request?
如果我使用这样的约束 @NotNull 然后在控制器中
public User createUser(
@Validated
@RequestBody User user) {}
它提供了一个非常好的 400 异常细节。
但是如果我像这样使用自己的自定义验证器:
public User createUser(
@UserConstraint
@RequestBody User user) {}
它抛出 500 服务器错误,如下所示:
javax.validation.ConstraintViolationException: createUser.user: Error with field: 'test35'
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:117) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69) ~[spring-security-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
有没有办法让 nice 400 消息响应?
理想情况下,400 消息应与 Spring 的验证相同 JSON
{
"timestamp": "2019-10-30T02:33:15.489+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Size.user.lastName",
"Size.lastName",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"user.lastName",
"lastName"
],
"arguments": null,
"defaultMessage": "lastName",
"code": "lastName"
},
25,
1
],
"defaultMessage": "size must be between 1 and 25",
"objectName": "user",
"field": "lastName",
"rejectedValue": "",
"bindingFailure": false,
"code": "Size"
}
],
"message": "Validation failed for object='user'. Error count: 1",
"path": "/api/v1/users"
}
简单的,在一个用@ControllerAdvice
注解的class中定义一个用@ExceptionHandler
注解的方法:
@ControllerAdvice
public class YourControllerAdvice {
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public void handleConstraintViolationException() {
// Intentionally left blank
}
}
类注解@ControllerAdvice
用于处理controller级别的异常。
是的,您可以创建一个 custom error handler
然后您还可以在您的回复和状态中添加任何内容。这是更改状态的简单方法:
1.- 抛出 ConstraintViolationException
时更改 status
的简单方法。
import javax.validation.ConstraintViolationException;
@ControllerAdvice
public class CustomErrorHandler {
@ExceptionHandler(ConstraintViolationException.class)
public void handleConstraintViolationException(ConstraintViolationException exception,
ServletWebRequest webRequest) throws IOException {
webRequest.getResponse().sendError(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
}
}
2.- 当 ConstraintViolationException
发生时自定义放置响应的方式。
@ControllerAdvice
public class CustomErrorHandler {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<CustomError> handleConstraintViolationException(ConstraintViolationException exception) {
CustomError customError = new CustomError();
customError.setStatus(HttpStatus.BAD_REQUEST);
customError.setMessage(exception.getMessage());
customError.addConstraintErrors(exception.getConstraintViolations());
return ResponseEntity.badRequest().body(customError);
}
}
由于上面的解决方案并没有真正产生预期的结果,link 这可能会有所帮助:https://sterl.org/2020/02/spring-boot-hateoas-jsr303-validation/
有趣的是 spring 如果 class 或方法请求正文使用 @Validated 注释,则行为会有所不同。
也就是说在class上,你可能会遇到500个错误。如果像您已经做的那样将验证注释移动到方法中,则正常行为应该是 400。
长话短说,一旦您有了自定义包含等,您就需要稍微调整一下内容——如 Spring 中所示,它是 MethodArgumentNotValidException 而不是 ConstraintViolationException,为此 Spring 已经作为控制器建议。
快速解决方案可能如下所示:
@Autowired
private MessageSource messageSource;
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public @ResponseBody Map<String, Object> handleConstraintViolation(ConstraintViolationException e, ServletWebRequest request) {
// emulate Spring DefaultErrorAttributes
final Map<String, Object> result = new LinkedHashMap<>();
result.put("timestamp", new Date());
result.put("path", request.getRequest().getRequestURI());
result.put("status", HttpStatus.BAD_REQUEST.value());
result.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
result.put("message", e.getMessage());
result.put("errors", e.getConstraintViolations().stream().map(cv -> SimpleObjectError.from(cv, messageSource, request.getLocale())));
return result;
}
@Getter @ToString
static class SimpleObjectError {
String defaultMessage;
String objectName;
String field;
Object rejectedValue;
String code;
public static SimpleObjectError from(ConstraintViolation<?> violation, MessageSource msgSrc, Locale locale) {
SimpleObjectError result = new SimpleObjectError();
result.defaultMessage = msgSrc.getMessage(violation.getMessageTemplate(),
new Object[] { violation.getLeafBean().getClass().getSimpleName(), violation.getPropertyPath().toString(),
violation.getInvalidValue() }, violation.getMessage(), locale);
result.objectName = Introspector.decapitalize(violation.getRootBean().getClass().getSimpleName());
result.field = String.valueOf(violation.getPropertyPath());
result.rejectedValue = violation.getInvalidValue();
result.code = violation.getMessageTemplate();
return result;
}
}
如果我使用这样的约束 @NotNull 然后在控制器中
public User createUser(
@Validated
@RequestBody User user) {}
它提供了一个非常好的 400 异常细节。
但是如果我像这样使用自己的自定义验证器:
public User createUser(
@UserConstraint
@RequestBody User user) {}
它抛出 500 服务器错误,如下所示:
javax.validation.ConstraintViolationException: createUser.user: Error with field: 'test35'
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:117) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69) ~[spring-security-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
有没有办法让 nice 400 消息响应?
理想情况下,400 消息应与 Spring 的验证相同 JSON
{
"timestamp": "2019-10-30T02:33:15.489+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Size.user.lastName",
"Size.lastName",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"user.lastName",
"lastName"
],
"arguments": null,
"defaultMessage": "lastName",
"code": "lastName"
},
25,
1
],
"defaultMessage": "size must be between 1 and 25",
"objectName": "user",
"field": "lastName",
"rejectedValue": "",
"bindingFailure": false,
"code": "Size"
}
],
"message": "Validation failed for object='user'. Error count: 1",
"path": "/api/v1/users"
}
简单的,在一个用@ControllerAdvice
注解的class中定义一个用@ExceptionHandler
注解的方法:
@ControllerAdvice
public class YourControllerAdvice {
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public void handleConstraintViolationException() {
// Intentionally left blank
}
}
类注解@ControllerAdvice
用于处理controller级别的异常。
是的,您可以创建一个 custom error handler
然后您还可以在您的回复和状态中添加任何内容。这是更改状态的简单方法:
1.- 抛出 ConstraintViolationException
时更改 status
的简单方法。
import javax.validation.ConstraintViolationException;
@ControllerAdvice
public class CustomErrorHandler {
@ExceptionHandler(ConstraintViolationException.class)
public void handleConstraintViolationException(ConstraintViolationException exception,
ServletWebRequest webRequest) throws IOException {
webRequest.getResponse().sendError(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
}
}
2.- 当 ConstraintViolationException
发生时自定义放置响应的方式。
@ControllerAdvice
public class CustomErrorHandler {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<CustomError> handleConstraintViolationException(ConstraintViolationException exception) {
CustomError customError = new CustomError();
customError.setStatus(HttpStatus.BAD_REQUEST);
customError.setMessage(exception.getMessage());
customError.addConstraintErrors(exception.getConstraintViolations());
return ResponseEntity.badRequest().body(customError);
}
}
由于上面的解决方案并没有真正产生预期的结果,link 这可能会有所帮助:https://sterl.org/2020/02/spring-boot-hateoas-jsr303-validation/
有趣的是 spring 如果 class 或方法请求正文使用 @Validated 注释,则行为会有所不同。
也就是说在class上,你可能会遇到500个错误。如果像您已经做的那样将验证注释移动到方法中,则正常行为应该是 400。
长话短说,一旦您有了自定义包含等,您就需要稍微调整一下内容——如 Spring 中所示,它是 MethodArgumentNotValidException 而不是 ConstraintViolationException,为此 Spring 已经作为控制器建议。
快速解决方案可能如下所示:
@Autowired
private MessageSource messageSource;
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public @ResponseBody Map<String, Object> handleConstraintViolation(ConstraintViolationException e, ServletWebRequest request) {
// emulate Spring DefaultErrorAttributes
final Map<String, Object> result = new LinkedHashMap<>();
result.put("timestamp", new Date());
result.put("path", request.getRequest().getRequestURI());
result.put("status", HttpStatus.BAD_REQUEST.value());
result.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
result.put("message", e.getMessage());
result.put("errors", e.getConstraintViolations().stream().map(cv -> SimpleObjectError.from(cv, messageSource, request.getLocale())));
return result;
}
@Getter @ToString
static class SimpleObjectError {
String defaultMessage;
String objectName;
String field;
Object rejectedValue;
String code;
public static SimpleObjectError from(ConstraintViolation<?> violation, MessageSource msgSrc, Locale locale) {
SimpleObjectError result = new SimpleObjectError();
result.defaultMessage = msgSrc.getMessage(violation.getMessageTemplate(),
new Object[] { violation.getLeafBean().getClass().getSimpleName(), violation.getPropertyPath().toString(),
violation.getInvalidValue() }, violation.getMessage(), locale);
result.objectName = Introspector.decapitalize(violation.getRootBean().getClass().getSimpleName());
result.field = String.valueOf(violation.getPropertyPath());
result.rejectedValue = violation.getInvalidValue();
result.code = violation.getMessageTemplate();
return result;
}
}