Вступление
Spring Boot предоставляет отличный механизм обработки исключений из коробки. Реализация по умолчанию ErrorController
делает хорошую работу по отлову и обработке исключений. Кроме того, мы все еще можем определять свои собственные, @ExceptionHandler
чтобы ловить и обрабатывать определенные исключения Но есть еще возможности для улучшения:
- Даже если мы решим обработать все исключения с помощью пользовательского интерфейса
@ExceptionHandler
, некоторые исключения все же удастся избежать этого обработчика, иErrorController
они будут отвечать за обработку исключений. Это@ExceptionHandler
противErrorController
двойственности, безусловно, может быть улучшено. - Иногда представление ошибок по умолчанию выглядит немного грязно:
{
"timestamp": "2018-09-23T15:05:32.681+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.dto.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"dto.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "{name.not_blank}",
"objectName": "dto",
"field": "name",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='dto'. Error count: 1",
"path": "/"
}
Конечно, мы можем улучшить это, зарегистрировав пользовательскую ErrorAttributes
реализацию, но было бы желательно более разумное и взвешенное (и все еще легко настраиваемое) представление по умолчанию.
- Для ошибок проверки можно легко представить некоторые аргументы из ограничения в интерполированное сообщение. Например, минимальное количество для
int
значения может быть передано в интерполированное сообщение с использованием{}
синтаксиса заполнителя. Но то же самое не верно для других исключений:
public class UserAlreadyExistsException extends RuntimeException {
// How can I expose this value to the interpolated message?
private final String username;
// constructors and getters and setters
}
- Было бы хорошо, если бы у нас была встроенная поддержка кодов ошибок уровня приложения для всех исключений. Иногда, просто используя соответствующий код состояния HTTP, мы не можем выяснить, что именно пошло не так. Например, что если две совершенно разные ошибки в одной и той же конечной точке имеют одинаковый код состояния?
Есть куда расти
Это errors-spring-boot-starter
попытка обеспечить гибкий, последовательный и самоуверенный подход для обработки всевозможных исключений. Созданные на основе механизма обработки исключений Spring Boot, errors-spring-boot-starter
предложения:
- Последовательный подход для обработки всех исключений — не имеет значения, является ли это ошибкой валидации / привязки, пользовательской специфичной для домена ошибкой или даже ошибкой, связанной с Spring. Все они будут обработаны реализацией WebErrorHandler (больше не ErrorController vs @ExceptionHandler)
- Встроенная поддержка специфичных для приложения кодов ошибок, опять же, для всех возможных ошибок.
- Простая интерполяция сообщений об ошибках с использованием MessageSource.
- Настраиваемое представление ошибок HTTP.
- Предоставление аргументов из исключений сообщениям об ошибках.
Представление ошибок по умолчанию
По умолчанию ошибки будут результатом JSON со следующей схемой:
// For each error, there is a code/messsge combination in the errors array
{
"errors": [
{
"code": "first_error_code",
"message": "1st error message"
}
]
}
Чтобы настроить это представление, просто зарегистрируйте HttpErrorAttributesAdapter
реализацию как Spring Bean .
Согласованный подход к обработке ошибок
Все исключения будут обработаны WebErrorHandler
реализацией. По умолчанию этот стартер будет обращаться к нескольким встроенным WebErrorHandler
методам для обработки следующих конкретных исключений:
- Реализация для обработки всех исключений валидации / связывания.
- Реализация для обработки пользовательских исключений, аннотированных с помощью
@ExceptionMapping
. - Реализация для обработки особых исключений Spring MVC.
- И если Spring Security находится на пути к классам, реализация для обработки особых исключений Spring Security.
Вы можете легко зарегистрировать свой собственный обработчик исключений, реализовав WebErrorHandler
реализацию и зарегистрировав ее как Spring Bean .
Поддержка встроенного кода ошибки
Хотя использование соответствующих кодов состояния HTTP является рекомендуемым подходом в API-интерфейсах RESTful, иногда нам требуется дополнительная информация, чтобы выяснить, что именно пошло не так. Вот тут и появляются коды ошибок. Код ошибки можно рассматривать как машиночитаемое описание ошибки. Каждое исключение может быть сопоставлено как минимум с одним кодом ошибки.
В исключении-к-коду ошибки отображения варьироваться в зависимости от типа исключений:
- Коды ошибок валидации будут извлечены из
message
атрибута соответствующей аннотации ограничения, например@NotBlank(message = "name.required")
. errorCode
Атрибут для исключения аннотированного с@ExceptionMapping
выглядит следующим образом :
@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {}
- Вот код из пользовательских реализаций
WebErrorHandler
:
public class ExistedUserHandler implements WebErrorHandler {
@Override
public boolean canHandle(Throwable exception) {
return exception instanceof UserAlreadyExistsException;
}
@Override
public HandledException handle(Throwable exception) {
return new HandledException("user.already_exists", BAD_REQUEST, null);
}
}
Разоблачение аргументов
Как и в Spring Boot, вы можете передавать аргументы проверки из аннотации ограничения, например @Min(value = 18, message = "age.min")
, в сообщение, подлежащее интерполяции :
age.min = The minimum age is {0}!
Кроме того, чтобы поддержать эту функцию для ошибок проверки, мы расширяем ее для пользовательских исключений, используя @ExposeAsArg
аннотацию. Например, если мы собираемся указать уже введенное имя пользователя в следующем сообщении:
user.already_exists=Another user with the '{0}' username already exists
Ты можешь написать:
@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {
@ExposeAsArg(0) private final String username;
// constructor
}
Заключение
В этой статье мы перечислили несколько возможных улучшений по сравнению с подходом обработки исключений в Spring Boot, представив errors-spring-boot-starter
стартовый пакет. Для получения дополнительной информации об этом стартере, проверьте этот репозиторий GitHub .