Статьи

Обработка исключений для REST с Spring 3.2

1. Обзор

Эта статья будет посвящена реализации обработки исключений с помощью Spring для REST API . Мы рассмотрим более старые решения, доступные до Spring 3.2, а затем новую поддержку Spring 3.2.

Основная цель этой статьи — показать, как лучше всего сопоставить исключения в приложении с кодами состояния HTTP. Какие коды состояния подходят для каких сценариев не входит в сферу действия данной статьи, также как и синтаксис REST Error представление.

До Spring 3.2 двумя основными подходами к обработке исключений в приложении Spring MVC были: HandlerExceptionResolver и аннотация @ExceptionHandler . Spring 3.2 представил новую аннотацию @ControllerAdvice, чтобы устранить ограничения предыдущих двух решений.

Все они имеют одну общую черту — они очень хорошо справляются с разделением задач : стандартный код приложения может генерировать исключение, как правило, для обозначения какого-либо сбоя — исключения, которые затем будут обрабатываться с помощью любого из следующих действий.

2. Через уровень контроллера @ExceptionHandler

Определить метод уровня контроллера, аннотированный @ExceptionHandler , очень просто:

1
2
3
4
5
6
7
public class FooController{
    ...
    @ExceptionHandler({ CustomException1.class, CustomException2.class })
    public void handleException() {
        //
    }
}

Это все хорошо, но у этого подхода есть один существенный недостаток — аннотированный метод @ExceptionHandler активен только для этого конкретного контроллера , а не глобально для всего приложения. Конечно, это делает его не очень подходящим для универсального механизма обработки исключений.

Распространенным решением для этого является использование всех контроллеров в приложении, расширяющих базовый класс контроллеров, однако это может стать проблемой для приложений, в которых по каким-либо причинам нельзя сделать так, чтобы контроллеры расширялись от такого класса. Например, контроллеры могут уже расширяться из другого базового класса, который может находиться в другом фляге или не может быть напрямую изменен, или сами могут не быть непосредственно модифицируемыми.

Далее мы рассмотрим другой способ решения проблемы обработки исключений — тот, который является глобальным и не содержит каких-либо изменений в существующих артефактах, таких как контроллеры.

3. Через HandlerExceptionResolver

Чтобы реализовать единый механизм обработки исключений в нашем REST API, нам нужно будет работать с HandlerExceptionResolver — это разрешит любые исключения, выданные приложением во время выполнения. Прежде чем перейти к пользовательскому распознавателю, давайте рассмотрим существующие реализации.

3.1. ExceptionHandlerExceptionResolver

Этот преобразователь был представлен в Spring 3.1 и по умолчанию включен в DispatcherServlet . На самом деле это основной компонент того, как работает механизм @ ExceptionHandler , представленный ранее.

3.2. DefaultHandlerExceptionResolver

Этот распознаватель был представлен в Spring 3.0 и по умолчанию включен в DispatcherServlet . Он используется для разрешения стандартных исключений Spring в соответствующих кодах состояния HTTP, а именно: ошибка клиента — 4xx и ошибка сервера — коды состояния 5xx . Вот полный список исключений Spring, которые он обрабатывает, и их сопоставление с кодами состояния.

Хотя он правильно устанавливает код состояния ответа, одно из ограничений этого преобразователя состоит в том, что он ничего не устанавливает в теле ответа. Однако в контексте REST API кода состояния на самом деле недостаточно, чтобы предоставить его клиенту — ответ также должен иметь тело, чтобы приложение могло предоставить дополнительную информацию о причине сбоя.

Эту проблему можно решить, настроив разрешение представления и отображая содержимое ошибок с помощью ModelAndView , но решение явно не оптимально — поэтому в Spring 3.2 появился лучший вариант — об этом мы поговорим в последней части этой статьи. ,

3.3. ResponseStatusExceptionResolver

Этот распознаватель также был представлен в Spring 3.0 и по умолчанию включен в DispatcherServlet . Его главная ответственность — использовать аннотацию @ResponseStatus, доступную для пользовательских исключений, и сопоставить эти исключения с кодами состояния HTTP.

Такое пользовательское исключение может выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public final class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException() {
        super();
    }
    public ResourceNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    public ResourceNotFoundException(String message) {
        super(message);
    }
    public ResourceNotFoundException(Throwable cause) {
        super(cause);
    }
}

Как и DefaultHandlerExceptionResolver , этот преобразователь ограничен в способе работы с телом ответа — он отображает код состояния в ответе, но тело все еще равно нулю.

3.4. SimpleMappingExceptionResolver и AnnotationMethodHandlerExceptionResolver

SimpleMappingExceptionResolver существует уже довольно давно — он взят из старой модели Spring MVC и не очень актуален для службы REST . Он используется для отображения имен классов исключений для просмотра имен.

AnnotationMethodHandlerExceptionResolver был введен в Spring 3.0 для обработки исключений с помощью аннотации @ExceptionHandler , но исключен ExceptionHandlerExceptionResolver начиная с Spring 3.2.

3.5. Пользовательский HandlerExceptionResolver

Комбинация DefaultHandlerExceptionResolver и ResponseStatusExceptionResolver имеет большое значение для обеспечения хорошего механизма обработки ошибок для Spring RESTful Service — но главное ограничение — отсутствие контроля над телом ответа — оправдывает создание нового обработчика исключений .

Итак, одна цель для нового распознавателя — включить настройку более информативного тела ответа, которое также будет соответствовать типу представления, запрошенному клиентом (как указано в заголовке Accept ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
 
    @Override
    protected ModelAndView doResolveException
      (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            if (ex instanceof IllegalArgumentException) {
                return handleIllegalArgument((IllegalArgumentException) ex, response, handler);
            }
            ...
        } catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "]
              resulted in Exception", handlerException);
        }
        return null;
    }
 
    private ModelAndView handleIllegalArgument
      (IllegalArgumentException ex, HttpServletResponse response) throws IOException {
        response.sendError(HttpServletResponse.SC_CONFLICT);
        String accept = request.getHeader(HttpHeaders.ACCEPT);
        ...
        return new ModelAndView();
    }
}

Здесь следует отметить одну деталь: сам запрос доступен, поэтому приложение может учитывать значение заголовка Accept, отправленного клиентом. Например, если клиент запрашивает приложение / json, то в случае возникновения ошибки приложение все равно должно вернуть тело ответа, закодированное с помощью application / json .

Другая важная деталь реализации заключается в том, что возвращается ModelAndView — это тело ответа, и оно позволит приложению устанавливать для него все необходимое.

Этот подход представляет собой согласованный и легко настраиваемый механизм обработки ошибок службы Spring REST. Однако у него есть ограничения : он взаимодействует с HtttpServletResponse низкого уровня и вписывается в старую модель MVC, которая использует ModelAndView — так что еще есть возможности для улучшения.

4. Через новый @ControllerAdvice (только для версии 3.2)

В Spring 3.2 появилась глобальная поддержка @ExceptionHandler с новой аннотацией @ControllerAdvice . Это включает механизм, который отрывается от старой модели MVC и использует ResponseEntity наряду с безопасностью типов и гибкостью @ExceptionHandler :

01
02
03
04
05
06
07
08
09
10
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
 
    @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })
    protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
        String bodyOfResponse = "This should be application specific";
        return handleExceptionInternal(ex, bodyOfResponse,
          new HttpHeaders(), HttpStatus.CONFLICT, request);
    }
}

Новая аннотация позволяет консолидировать несколько рассеянных @ExceptionHandler до этого в один глобальный компонент обработки ошибок .

Фактический механизм чрезвычайно прост, но также и очень гибок:

  • это позволяет полностью контролировать тело ответа, а также код состояния
  • это позволяет сопоставить несколько исключений одному и тому же методу для совместной обработки
  • это хорошо использует более новый ответ RESTful ResposeEntity

5. Заключение

В этом руководстве обсуждалось несколько способов реализации механизма обработки исключений для REST API в Spring, начиная с более старого механизма и продолжая с новой поддержкой Spring 3.2. Для полной реализации этих механизмов обработки исключений, работающих в реальной службе REST, посмотрите проект github .

Ссылка: Обработка ошибок для REST с Spring 3 от нашего партнера JCG Евгения Параскива в блоге baeldung .