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 .