【问题标题】:Catch a deserialization exception before a ControllerAdvice在 ControllerAdvice 之前捕获反序列化异常
【发布时间】:2018-12-02 17:38:12
【问题描述】:

这里有一个问题:我有一个控制器,它采用输入模型。让我们说

public class AppUserUpdateData {

  @NotNull
  @Size(min = 1, max = 50)
  protected String login;  
  @JsonDeserialize(using = MyDateTimeDeserializer.class)  
  protected Date startWorkDate;
  *************
  other properties and methods
  *************
}

问题是当我想限制一个日期的下板时,尽管我在我的代码中处理了这种情况,但我最终得到了一个 HTTP 异常 400,没有任何消息! 这是一个控制器:

 @RequestMapping(
      value = "/users/{userId}", method = RequestMethod.PUT,
      produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public @ResponseBody AbstractSuccessResult updateUser(@PathVariable Long userId,
      @RequestBody AppUserUpdateData  appUserUpdateRequest, HttpServletRequest request) {    
    AbstractSuccessResult response = new AbstractSuccessResult();
    appUserService.updateUser(appUserUpdateRequest, userId);
    return response;
  }

这是一个反序列化器:

public class MyDateTimeDeserializer extends JsonDeserializer<Date> {

  @Override
  public Date deserialize(JsonParser jsonParser, DeserializationContext context)
      throws IOException, JsonProcessingException {
    try {
      return DataTypeHelper.stringToDateTime(jsonParser.getText());
    } catch (MyOwnWrittenException ex) {
      throw ex;
    }
  }  
}

DataTypeHelper.stringToDateTime 中有一些阻止无效日期字符串的验证。 还有一个我的异常的处理程序:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler({ MyOwnWrittenException .class})
  protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc, 
    WebRequest request) {

    MyOwnWrittenException ex = (MyOwnWrittenException) exc;
    BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());    
    AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
  }
}

问题是,当MyDateTimeDeserializer 中的异常被抛出时,它不会落入MyExceptionHandler,但我不明白为什么?我究竟做错了什么? 响应中只是一个空响应,代码为 400(

UPD 感谢@Joe Doe 的回答,问题已经解决。这是我更新的处理程序:

@Order(Ordered.HIGHEST_PRECEDENCE)    
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler({ MyOwnWrittenException .class})
  protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc, 
    WebRequest request) {

    MyOwnWrittenException ex = (MyOwnWrittenException) exc;
    BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());    
    AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
  }

  @Override
  protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
      HttpHeaders headers, HttpStatus status, WebRequest request) {
    Throwable cause = ex.getCause();
    String message = null;
    if (cause instanceof JsonMappingException) {
      if (cause.getCause() instanceof MyOwnWrittenException) {
        return handleInvalidRequest((RuntimeException) cause.getCause(), request);
      } else {
        message = cause.getMessage();
      }
    } else {
      message = ex.getMessage();
    }
    AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(
        new BasicErrorMessage(message));
    headers.setContentType(MediaType.APPLICATION_JSON);
    return handleExceptionInternal(ex, result, headers, HttpStatus.BAD_REQUEST, request);
  }
}

UPD 在我的项目中,如果没有注释@Order(Ordered.HIGHEST_PRECEDENCE),它就无法工作 我相信这是因为一个项目中有很多 ControllerAdvices

【问题讨论】:

  • 您能否同时发布MyOwnWrittenExceptionBasicErrorMessageAbstractUnsuccessfulResult?此外,我认为您的事情过于复杂,不需要这样做,但请发布以上内容以检查您的代码。

标签: java spring spring-mvc spring-boot


【解决方案1】:

在调用控制器中的updateUser 之前,必须解析其参数。这就是HandlerMethodArgumentResolverComposite 出现的地方,并委托给预先注册的HandlerMethodArgumentResolvers 之一——在这种特殊情况下,它委托给RequestResponseBodyMethodProcessor

委托我的意思是调用解析器的resolveArgument 方法。此方法间接从您的反序列化器调用deserialize 方法,这会引发MyOwnWrittenException 类型的异常。问题是这个异常被包裹在另一个异常中。事实上,当它传播回resolveArgument 时,它的类型是HttpMessageNotReadableException

因此,您需要捕获HttpMessageNotReadableException 类型的异常,而不是在自定义异常处理程序中捕获MyOwnWrittenException。然后,在处理这种情况的方法中,您可以检查“原始”异常是否实际上是 MyOwnWrittenException - 您可以通过重复调用 getCause 方法来做到这一点。在我的情况下(在你的情况下可能会相同),我需要调用 getCause 两次以“解开”原始异常(HttpMessageNotReadableException -> JsonMappingException -> MyOwnWrittenException)。

请注意,您不能在异常处理程序中简单地将MyOwnWrittenException 替换为HttpMessageNotReadableException,因为它(在运行时)与另一种方法发生冲突,该方法专门用于处理后一种类型的异常,称为handleHttpMessageNotReadable

总而言之,您可以这样做:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // ex.getCause().getCause().getClass() gives MyOwnWrittenException
        // the actual logic that handles the exception...
    }
}

【讨论】:

  • 是的,它有帮助!非常感谢!
  • 有效!我承认:)
猜你喜欢
  • 2012-09-22
  • 2020-08-04
  • 2011-08-21
  • 2015-05-04
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
相关资源
最近更新 更多