Поймать исключение десериализации перед ControllerAdvice

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

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 в проекте.

Не могли бы вы также выложить MyOwnWrittenException, BasicErrorMessage и AbstractUnsuccessfulResult? Кроме того, я думаю, что у вас есть слишком сложные вещи, и в этом нет особой необходимости, но, пожалуйста, опубликуйте выше, чтобы проверить свой код.

akortex91 23.06.2018 16:23
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
7
1
2 075
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прежде чем 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...
    }
}

Да, помогло! Большое спасибо!

Yuriy Tsarkov 24.06.2018 12:16

Работает! Я признаю:)

Matley 03.03.2021 13:16

Другие вопросы по теме