我如何确保解析期间的异常导致与验证失败返回的(自定义)响应相同类型的响应?

How can I make sure exceptions during parsing lead to the same kind of response as the (custom) response returned for validation failures?

我正在使用 Spring 创建一个 API,但我在引入自定义错误报告(部分)请求正文验证时遇到了一些问题。

当出现 parsing/validation 错误时,我想给用户一个自定义响应。
通过使用带有 @ControllerAdvice.
注释的自定义 ResponseEntityExceptionHandler,这适用于带有 @Valid 注释的字段以及 @javax.validation.constraints.NotNull 等验证器 但是,如果在解析请求正文时抛出异常(甚至在验证 运行 之前),它就不起作用。在那种情况下,我得到一个 html 状态为 500 (Server Error)

的错误页面

如何确保解析期间的异常导致与我 return 验证失败时的(自定义)响应相同类型的响应?


我的端点代码如下所示:

@RequestMapping(value= "/endpoint"
    produces = { "application/json" }, 
    consumes = { "application/json" },
    method = RequestMethod.POST)
default ResponseEntity<Object> postSomething(@Valid @RequestBody MyRequestBody requestData){
    // ...
}

MyRequestBody class 看起来像这样:

@Validated
public class MyRequestData {

    @JsonProperty("stringValue")
    private String stringValue = null;

    @NotNull
    @Valid
    public String getStringValue() {
        return stringValue;
    }

    // ...

    public enum EnumValueEnum {
        VALUE_1("value 1"),
        VALUE_1("value 2");

        private String value;

        EnumValueEnum(String value) {
            this.value = value;
        }

        @Override
        @JsonValue
        public String toString() {
            return String.valueOf(value);
        }

        @JsonCreator
        public static EnumValueEnum fromValue(String text) {
          if(text == null){
            return null;
          }
          for (EnumValueEnum b : EnumValueEnum.values()){
            if (String.valueOf(b.value).equals(text)) {
              return b;
            }
          }
          throw new HttpMessageNotReadableException("EnumValueEnum \"" + text + "\" does not exist");
        }

    }

    @JsonProperty("enumValue")
    private EnumValueEnum enumValue = null;
}

自定义验证错误处理(和报告)如下所示:

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class MyValidationHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // return status(BAD_REQUEST).body(new ValidationResponse(ex.getBindingResult().getFieldErrors()));
    }

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) ex.getCause()));
    }
}

在此代码中,如果用户发送带有不存在的枚举值的请求,则会抛出 HttpMessageNotReadableException。我想在某个地方捕获它,并将其替换为与我所做的其他异常处理一致的自定义响应。 Where/How我可以这样做吗?

我找到了解决我自己问题的方法。

其实你可以使用Spring MVC的正常异常处理:

@ExceptionHandler 注释一个方法将使 Spring 尝试使用它来处理指定的异常类型(在注释的值字段或方法的参数中)。此方法可以放在控制器中,甚至可以放在我用于其他验证响应处理的 ResponseEntityExceptionHandler 中。

@ExceptionHandler
public ResponseEntity handle(HttpMessageConversionException e){
    // return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) e.getCause()));
}

注意你处理的异常类型:

这里的问题是解析时抛出的异常被包装在 JsonMappingException 的(某些子类型)中,而后者又被包装在 HttpMessageConversionException.

e instanceof HttpMessageConversionException
e.getCause() instanceof JsonMappingException
e.getCause().getCause() // == your original exception

因此 @ExceptionHandler 应该接受 HttpMessageConversionException 而不是最初抛出的异常 (在我的例子中是 HttpMessageNotReadableException

如果你写一个只接受你原来的异常的 @ExceptionHandler 将不起作用!