Как убедиться, что @ExceptionHandler(Exception.class) будет вызван последним в весенней загрузке?

#java #spring #spring-boot #exception

Вопрос:

У меня есть контроллер, обрабатывающий некоторые запросы:

 @RequestMapping(method = RequestMethod.POST, value = "/endpoint")
public ResponseEntity<MyResponse> myEndpoint(@RequestBody MyRequest request) throws Exception {
    //...
}
 

Такой контроллер может выдавать несколько исключений, и для этого я использую @ExceptionHandler этот способ:

 @ExceptionHandler(SomeSpecificException.class)
@ResponseBody
public ResponseEntity<Error> handleSomeSpeficicFailure(SomeSpecificException e) {
    //handle specific exception
}

@ExceptionHandler(SomeOtherSpecificException.class)
@ResponseBody
public ResponseEntity<Error> handleSomeOtherSpeficicFailure(SomeOtherSpecificException e) {
    //handle specific exception
}

//etc.
 

Если создаваемое исключение не принадлежит ни к одному из известных классов, я добавил универсальный Exception.class обработчик, который возвращает пользовательское значение 500:

 @ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Error> handleUnknownFailure(Exception e) {
    //handle unknown exception
}
 

Проведя несколько тестов, кажется, все работает нормально. Если я создаю определенное исключение, меня вызывают к определенному обработчику, а если я создаю несопоставимое исключение, меня вызывают к универсальному обработчику.

Однако я не вижу никакого упоминания (ни в JavaDoc, ни в документации Spring) о гарантии того, что сначала меня вызовут по конкретным методам, а затем по общему методу.

Если бы Spring проверял , что есть конкретное исключение instanceof Exception , это было бы правдой, поэтому он может даже сначала вызвать меня к этому обработчику, не проверяя другие.

Мои вопросы таковы:

  • Кто-нибудь знает, рекомендуется ли добавлять a @ExceptionHandler(Exception.class) для обработки общих исключений? Если нет, то как правильно это сделать?
  • Как я могу гарантировать , что иерархия классов будет соблюдаться (например, если однажды я это сделаю SomeVerySpecificException extends SomeSpecificException , как Весна узнает, что она должна позвонить мне по SomeSpecificException прямому родительскому — до Exception — бабушки и дедушки)?

Ответ №1:

После отладки я нашел ответ в их коде — хотя это и не задокументировано, жаль.

 @Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
    List<Class<? extends Throwable>> matches = new ArrayList<>();
    for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
        if (mappedException.isAssignableFrom(exceptionType)) {
            matches.add(mappedException);
        }
    }
    if (!matches.isEmpty()) {
        matches.sort(new ExceptionDepthComparator(exceptionType));
        return this.mappedMethods.get(matches.get(0));
    }
    else {
        return null;
    }
}
 

Таким образом , в основном для данного Exception , они сначала ищут все те сопоставленные методы, для которых mappedException.isAssignableFrom(exceptionType) .

Как только этот список будет создан, затем:

  • Если список пуст, они возвращаются null и позволяют обрабатывать ошибки по умолчанию
  • Если содержит только один элемент, они возвращают его
  • Если содержит более одного элемента, они сортируют их по глубине (от ближайшего к самому дальнему расширению) и возвращают первый метод.

Таким образом, гарантия не дается по контракту, но она находится в процессе реализации, и кажется, что это действительно хорошо сделано.