Поймать исключение десериализации перед ControllerAdvice - PullRequest
0 голосов
/ 23 июня 2018

Вот проблема: у меня есть контроллер, который принимает модель ввода. Скажем

public class AppUserUpdateData {

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

Проблема в том, что когда я хочу ограничить переход на новую дату, я в итоге получаю исключение 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 в проекте

1 Ответ

0 голосов
/ 23 июня 2018

Прежде чем updateUser в вашем контроллере будет вызван, его аргументы должны быть разрешены. Именно здесь приходит HandlerMethodArgumentResolverComposite и делегирует одному из предварительно зарегистрированных HandlerMethodArgumentResolver с - в данном конкретном случае он делегирует RequestResponseBodyMethodProcessor.

Под делегированием я подразумеваю вызов метода решателя resolveArgument. Этот метод косвенно вызывает метод deserialize из вашего десериализатора, который выдает исключение типа MyOwnWrittenException. Проблема в том, что это исключение заключено в другое исключение. Фактически, к тому времени, когда он распространяется обратно к resolveArgument, он имеет тип HttpMessageNotReadableException.

Таким образом, вместо того, чтобы перехватывать MyOwnWrittenException в обработчике пользовательских исключений, вам нужно перехватывать исключения типа HttpMessageNotReadableException. Затем в методе, который обрабатывает этот случай, вы можете проверить, было ли «оригинальное» исключение на самом деле 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...
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...