Очень важно правильно обрабатывать исключения, создаваемые программами REST API, и возвращать удобную информацию об исключении, поскольку это может помочь клиентам API правильно реагировать на проблемы на стороне сервера. Это помогает улучшить качество обслуживания REST API. Информация об исключениях, возвращаемая Spring Boot по умолчанию, неясна для клиентов API, и только разработчики будут обращать внимание на эти отчеты об исключениях стека. В этой лекции мы разберемся, как обрабатывать информацию об исключениях Spring REST API.
В последнее время Spring Boot стал знаменитостью в кругу разработчиков Java, и все больше и больше разработчиков выбирают Spring Boot для создания REST API. Использование Spring Boot может помочь разработчикам снизить нагрузку на написание кода шаблона и файлов конфигурации. Готовые функции Spring Boot популярны среди разработчиков. В этой лекции в упрощенном демонстрационном проекте я сосредоточусь на некоторых навыках обработки исключений Spring Boot REST API.
Если вы не хотите читать этот контент, а просто хотите быстро получить соответствующий исходный код, вы можете сразу перейти к концу статьи и найти ссылку на репозиторий Gihub, по которой вы можете легко получить весь исходный код этот контент.
1. Определите четко определенную информацию об исключении
Когда программа отправляет ошибку, она не должна возвращать непонятную информацию отчета о стеке клиенту API, что в некотором смысле невежливо и безответственно. Теперь мы смоделируем такое требование, чтобы клиент API мог отправить запрос на сервер для получения одной или нескольких сведений о пользователе, а также мог отправить запрос на создание новой информации о пользователе. Ниже приведена приблизительная информация об API:
Имя API | инструкция |
---|---|
GET /users/{userId} | Получить информацию о пользователе на основе идентификатора пользователя, если он не найден, вернуть информацию об исключении пользователя, не найденного |
GET /users | В соответствии с входящим набором идентификаторов получить информацию о пользователе, если она не найдена, вернуть ненормальную информацию о ненайденном пользователе. |
POST /users | Создать нового пользователя |
Spring MVC предоставляет нам некоторые полезные функции, которые помогают нам разрешать информацию об исключениях системы и возвращать полезную подсказку клиенту API.
В качестве примера возьмем POST /users для создания нового пользователя, когда мы предоставим обычные пользовательские данные и запросим этот интерфейс, REST API вернет следующую подсказку:
{
"id": 2,
"username": "wukong",
"age": 52,
"height": 170
}
Теперь измените возраст пользователя на 200 лет, измените рост на 500 см, имя пользователя — rulai, запросите этот REST API здесь и просмотрите возвращенную информацию API:
{
"restapierror": {
"status": "BAD_REQUEST",
"timestamp": "2019-05-19 06:04:47",
"message": "Validation error",
"subErrors": [
{
"object": "user",
"field": "height",
"rejectedValue": 500,
"message": "用户身高不能超过250厘米"
},
{
"object": "user",
"field": "age",
"rejectedValue": 200,
"message": "用户年龄不能超过120岁"
}
]
}
}
Как показано выше, когда клиент API предоставляет неверные данные, REST API возвращает правильно сформированное сообщение об исключении, временная метка форматируется из исходной целочисленной временной метки в удобочитаемую дату + время, а подробности также подробно перечислены. .
2. Информация об исключении упаковки
Чтобы предоставить клиенту API удобочитаемую информацию об исключении формата JSON, нам нужно ввести в проект пакет зависимостей Jackson JSR 310 и использовать предоставленную им аннотацию @JsonFormat для преобразования даты и времени в Java в соответствии с нашим заданным Дата и время Шаблон времени для форматирования. Теперь добавьте следующие зависимости в файл Maven pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.8</version>
</dependency>
После того, как зависимости готовы, нам нужно предоставить класс-оболочку для информации об исключении: RestApiError. Он будет отвечать за инкапсуляцию информации об исключении, выдаваемой REST API:
public class RestApiError {
private HttpStatus status;
@JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private String message;
private String debugMessage;
private List<RestApiSubError> subErrors;
private RestApiError(){
timestamp = LocalDateTime.now();
}
RestApiError(HttpStatus status){
this();
this.status = status;
}
RestApiError(HttpStatus status,Throwable ex){
this();
this.status = status;
this.message = "Unexpected error";
this.debugMessage = ex.getLocalizedMessage();
}
RestApiError(HttpStatus status,String message,Throwable ex){
this();
this.status = status;
this.message = message;
this.debugMessage = ex.getLocalizedMessage();
}
}
- statusСвойства используются для записи статуса ответа. Наследует ли он все статусы HttpStatus, такие как 4xx и 5xx.
- timestampАтрибут, используемый для регистрации при отправке ошибки
- messageАтрибуты используются для записи настраиваемых сообщений об исключениях, обычно подсказок, удобных для клиентов API.
- debugMessageАтрибуты используются для регистрации более подробных отчетов об ошибках.
- subErrorsАтрибут используется для записи информации о подисключении, прикрепленной к исключению, такой как информация о проверке поля в объекте пользователя и т. д.
Класс RestApiSubError используется для записи более подробной информации об исключении, обычно это отчет об исключении для ошибок проверки поля в классах сущностей:
abstract class RestApiSubError{}
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
class RestApiValidationError extends RestApiSubError{
private String object;
private String field;
private Object rejectedValue;
private String message;
RestApiValidationError(String object,String message){
this.object = object;
this.message = message;
}
}
Класс RestApiSubError — это абстрактный пустой класс, и конкретное расширение будет реализовано в RestApiValidationError. Класс RestApiValidationError запишет отчет об ошибке проверки атрибута в классе сущностей (например, в объекте User в этой лекции).
Теперь давайте проверим API GET /users/1 и получим информацию о пользователе с идентификатором пользователя 1:
{
"id": 1,
"username": "ramostear",
"age": 28,
"height": 170
}
REST API успешно вернул информацию о пользователе. Затем мы передаем идентификатор пользователя, которого нет в системе, чтобы увидеть, какую информацию возвращает REST API:
GET /users/100
{
"restapierror": {
"status": "NOT_FOUND",
"timestamp": "2019-05-19 06:31:17",
"message": "User was not found for parameters {id=100}"
}
}
Из приведенной выше информации JSON мы видим, что при получении несуществующей информации о пользователе REST API возвращает удобную подсказку. Сначала мы протестировали предоставление нестандартной информации о возрасте и росте пользователя, а затем давайте проверим предоставление пустого имени пользователя и просмотрим информацию, возвращаемую REST API:
{
"restapierror": {
"status": "BAD_REQUEST",
"timestamp": "2019-05-19 06:37:46",
"message": "Validation error",
"subErrors": [
{
"object": "user",
"field": "username",
"rejectedValue": "",
"message": "不能为空"
}
]
}
}
3. Процесс обработки информации об исключении Spring Boot
Обработка Spring Boot информации об исключении REST API будет включать три аннотации:
- @RestController : аннотация, отвечающая за обработку конкретной логики работы REST API.
- @ExceptionHandler : аннотация, отвечающая за обработку исключений, возникающих в классах, аннотированных @RestController.
- @ControllerAdvice : аннотация, которая может централизовать методы, аннотированные @ExceptionHandler, в одном месте для обработки.
Аннотация @ControllerAdivice — это новая аннотация, добавленная в Spring 3.2, которая позволяет применять один метод, аннотированный аннотацией @ExceptionHandler, к нескольким контроллерам. Преимущество его использования заключается в том, что мы можем обрабатывать исключения, генерируемые несколькими контроллерами одновременно, в едином месте. исключение Когда доступно конкретное исключение, будет вызываться класс обработки информации об исключении по умолчанию для обработки исключения, созданного контроллером (класс обработки информации об исключении по умолчанию).
Ниже мы используем пример диаграммы процесса, чтобы более интуитивно понять весь процесс обработки информации об исключении контроллера Spring Application:
На рисунке синие стрелки представляют нормальный процесс запроса и ответа, а красные стрелки представляют ненормальный процесс запроса и ответа.
4. Пользовательский класс обработки информации об исключениях
Класс обработки информации об исключениях, который поставляется с Spring Framework, часто не может удовлетворить наши фактические потребности бизнеса, что требует от нас определения класса обработки информации об исключениях, который соответствует конкретной ситуации.В пользовательском классе обработки информации об исключениях мы можем инкапсулировать более подробные отчеты об исключениях. . . .
Настраивая класс обработки информации об исключениях, мы можем встать на плечи «гигантов» и быстро инкапсулировать собственный класс обработки информации об исключениях без необходимости создавать «колесо» с нуля. Теперь, чтобы быстро реализовать собственный класс обработки информации об исключении и заставить его работать правильно, мы можем напрямую расширить класс ResponseEntityExceptionHandler, предоставляемый Spring, чтобы определить класс обработки информации об исключении пользователя. ResponseEntityExceptionHandler уже предоставляет множество полезных функций, нам просто нужно расширить этот класс или переопределить предоставляемые им методы.
Открываем класс ResponseEntityExceptionHandler, мы видим следующий исходный код:
public abstract class ResponseEntityExceptionHandler {
//不支持的HTTP请求方法异常信息处理方法
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(...){...}
//不支持的HTTP媒体类型异常处理方法
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(...){...}
//不接受的HTTP媒体类型异常处方法
protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(...){...}
//请求路径参数缺失异常处方法
protected ResponseEntity<Object> handleMissingPathVariable(...){...}
//缺少servlet请求参数异常处理方法
protected ResponseEntity<Object> handleMissingServletRequestParameter(...){...}
//servlet请求绑定异常
protected ResponseEntity<Object> handleServletRequestBindingException(...){...}
//不支持转换
protected ResponseEntity<Object> handleConversionNotSupported(...){...}
//类型不匹配
protected ResponseEntity<Object> handleTypeMismatch(...){...}
//消息无法检索
protected ResponseEntity<Object> handleHttpMessageNotReadable(...){...}
//HTTP消息不可写
protected ResponseEntity<Object> handleHttpMessageNotWritable(...){...}
//方法参数无效
protected ResponseEntity<Object> handleMethodArgumentNotValid(...){...}
//缺少servlet请求部分
protected ResponseEntity<Object> handleMissingServletRequestPart(...){...}
//绑定异常
protected ResponseEntity<Object> handleBindException(...){...}
//没有发现处理程序异常
protected ResponseEntity<Object> handleNoHandlerFoundException(...){...}
//异步请求超时异常
@Nullable
protected ResponseEntity<Object> handleAsyncRequestTimeoutException(...){...}
//内部异常
protected ResponseEntity<Object> handleExceptionInternal(...){...}
}
Мы выборочно охватываем несколько часто используемых методов обработки исключений и добавляем собственные методы обработки исключений:
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
protected ResponseEntity<Object> handleUserNotFound(UserNotFoundException ex){
RestApiError apiError = new RestApiError(HttpStatus.NOT_FOUND);
apiError.setMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
String error = ex.getParameterName() + " parameter is missing";
return buildResponseEntity(new RestApiError(BAD_REQUEST, error, ex));
}
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
HttpMediaTypeNotSupportedException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getContentType());
builder.append(" media type is not supported. Supported media types are ");
ex.getSupportedMediaTypes().forEach(t -> builder.append(t).append(", "));
return buildResponseEntity(new RestApiError(HttpStatus.UNSUPPORTED_MEDIA_TYPE, builder.substring(0, builder.length() - 2), ex));
}
...
}
Класс UserNotFoundException — это наш настраиваемый класс информации об исключении.При выполнении запроса GET/users/{userIds} или GET/users, если в базе данных не существует информации о записи идентификатора, будет выдана информация об исключении UserNotFoundException, а будет установлен код состояния ответа NOT_FOUND. Исходный код UserNotFoundException выглядит следующим образом:
public class UserNotFoundException extends Exception {
public UserNotFoundException(Class clz,String...searchParams){
super(UserNotFoundException.generateMessage(clz.getSimpleName(),toMap(String.class,String.class,searchParams)));
}
private static String generateMessage(String entity, Map<String,String> searchParams){
return StringUtils.capitalize(entity)+
" was not found for parameters "+
searchParams;
}
private static <K,V> Map<K,V> toMap(Class<K> key,Class<V> value,Object...entries){
if(entries.length % 2 == 1){
throw new IllegalArgumentException("Invalid entries");
}
return IntStream.range(0,entries.length/2).map(i->i*2)
.collect(HashMap::new,
(m,i)->m.put(key.cast(entries[i]),value.cast(entries[i+1])),Map::putAll);
}
}
Следующий рисунок более наглядно иллюстрирует весь процесс пользовательской обработки исключений:
Когда в UserService возникает исключение, информация об исключении будет передана в UserController, а информация об исключении в это время захватывается Spring и передается методу обработки UserNotFoundException. UserNotFoundException инкапсулирует отчет об исключении в объект RestApiError и возвращает его клиенту API. С помощью этого метода клиент API получит логичный и понятный ответный отчет.
Весь исходный код этого курса был загружен наGithubрепозиторий, вы можете щелкнуть эту ссылку, чтобы получить исходный код:GitHub.com/Слеза Рамоса/S…
Оригинальный адрес:woohoo Рамос слеза.com/articles/tickets…