В этом руководстве мы рассмотрим пример обработки исключений Spring Boot, в котором используется @ControllerAdvice и @ExceptionHandler .
Вы также можете обработать исключение Restful API с помощью @RestControllerAdvice , пожалуйста, посетите: пример @RestControllerAdvice в Spring Boot
Эта статья первоначально опубликована по адресу Bezkoder .
Обработка исключений Rest API
Мы создали контроллер Rest для операций CRUD и метода finder. Давайте посмотрим на код: (пошаговая сборка Rest API находится в:
- Пример Spring Boot Data JPA + H2 CRUD
- Пример Spring Boot Data JPA + MySQL CRUD
- Пример Spring Boot Data JPA + PostgreSQL CRUD
- Весенняя загрузка данных JPA + SQL Server
- Пример Spring Boot Data JPA + Oracle
- Пример Spring Boot MongoDB CRUD
- Пример Spring Boot Cassandra CRUD/| )
@RestController
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public ResponseEntity> getAllTutorials(@RequestParam(required = false) String title) {
try {
...
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/tutorials/{id}")
public ResponseEntity getTutorialById(@PathVariable("id") long id) {
Optional tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@PutMapping("/tutorials/{id}")
public ResponseEntity updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
Optional tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
...
return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
...
@DeleteMapping("/tutorials/{id}")
public ResponseEntity deleteTutorial(@PathVariable("id") long id) {
try {
tutorialRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/tutorials")
public ResponseEntity deleteAllTutorials() {
// try and catch
}
@GetMapping("/tutorials/published")
public ResponseEntity> findByPublished() {
// try and catch
}
}
Вы можете видеть, что мы используем try/catch много раз для аналогичного исключения ( INTERNAL_SERVER_ERROR ), и также есть много случаев, которые возвращают NOT_FOUND .
Есть ли в любом случае, чтобы сделать их простыми, какой-либо способ быстро и гибко прикрепить сообщение об ошибке с ответом? Давайте решим эту проблему сейчас.
Обработка исключений с помощью рекомендаций контроллера весной
Spring поддерживает обработку исключений с помощью глобального обработчика исключений ( @ExceptionHandler ) с рекомендациями контроллера ( @ControllerAdvice ). Это позволяет использовать механизм, который делает ResponseEntity работа с безопасностью типов и гибкостью @ExceptionHandler :
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(value = {ResourceNotFoundException.class, CertainException.class})
public ResponseEntity resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorMessage message = new ErrorMessage(
status,
date,
ex.getMessage(),
description);
return new ResponseEntity(message, HttpStatus.NOT_FOUND);
}
}
Аннотация @ControllerAdvice является специализацией аннотации @Component , поэтому она автоматически определяется с помощью сканирования пути к классу. ControllerAdvice – это своего рода перехватчик, который окружает логику наших контроллеров и позволяет нам применять к ним некоторую общую логику.
Его методы (с аннотацией @ExceptionHandler ) совместно используются глобально несколькими компонентами @Controller для захвата исключений и преобразования их в HTTP-ответы. Аннотация @ExceptionHandler указывает, какой тип исключения мы хотим обработать. Экземпляр exception и request будут введены с помощью аргументов метода.
Используя две аннотации вместе, мы можем:
- управляйте телом ответа вместе с кодом состояния
- обрабатывать несколько исключений одним и тем же методом
@ResponseStatus Статус ответа
В приведенном выше примере мы используем @ControllerAdvice для веб-служб REST и возвращаем ResponseEntity объект дополнительно.
Spring также предоставляет аннотацию @ResponseBody , которая сообщает контроллеру, что возвращаемый объект автоматически сериализуется в JSON и передается объекту HttpResponse . Этот способ не требует ResponseEntity но вам нужно использовать @ResponseStatus , чтобы установить код состояния HTTP для этого исключения.
@ControllerAdvice
@ResponseBody
public class ControllerExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ErrorMessage resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorMessage message = new ErrorMessage(...);
return message;
}
}
Настройка проекта Весенней загрузки
Вы можете следовать шаг за шагом или получить исходный код в одном из следующих постов:
- Пример Spring Boot Data JPA + H2 CRUD
- Пример Spring Boot Data JPA + MySQL CRUD
- Пример Spring Boot Data JPA + PostgreSQL CRUD
- Весенняя загрузка данных JPA + SQL Server
- Пример Spring Boot Data JPA + Oracle
- Пример Spring Boot MongoDB CRUD
- Пример Spring Boot Cassandra CRUD
Проект Spring содержит структуру, в которую нам нужно только добавить некоторые изменения, чтобы разбивка на страницы работала хорошо.
Или вы можете получить новый исходный код Github в конце этого руководства.
Окончательная структура проекта будет выглядеть следующим образом:
Определить Сообщение Об Ошибке В Ответе
Мы хотим создать нашу собственную структуру ответа на сообщение вместо использования ответа на ошибку по умолчанию, предоставляемого Spring Boot. Давайте определим конкретную структуру ответов на ошибки.
исключение / ErrorMessage.java
package com.bezkoder.spring.exhandling.exception;
import java.util.Date;
public class ErrorMessage {
private int statusCode;
private Date timestamp;
private String message;
private String description;
public ErrorMessage(int statusCode, Date timestamp, String message, String description) {
this.statusCode = statusCode;
this.timestamp = timestamp;
this.message = message;
this.description = description;
}
public int getStatusCode() {
return statusCode;
}
public Date getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
}
Создать пользовательское исключение
Мы собираемся создать исключение для ресурса, не найденного в контроллере Spring Boot. Давайте создадим ResourceNotFoundException класс, который расширяет Исключение RuntimeException .
исключение / исключение
package com.bezkoder.spring.exhandling.exception;
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String msg) {
super(msg);
}
}
Создайте совет контроллера с помощью @ExceptionHandler
Теперь мы собираемся создать специальный класс, который аннотируется @ControllerAdvice аннотацией. Этот класс обрабатывает конкретное исключение ( Resourcenotfoundexception ) и глобальное исключение только в одном месте.
исключение / исключение
package com.bezkoder.spring.exhandling.exception;
import java.util.Date;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import com.bezkoder.spring.exhandling.exception.ErrorMessage;
import com.bezkoder.spring.exhandling.exception.ResourceNotFoundException;
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.NOT_FOUND.value(),
new Date(),
ex.getMessage(),
request.getDescription(false));
return new ResponseEntity(message, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity globalExceptionHandler(Exception ex, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
new Date(),
ex.getMessage(),
request.getDescription(false));
return new ResponseEntity(message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Измените контроллер для использования @ControllerAdvice
У вашего контроллера Rest теперь нет блока try/catch, и он выдаст ResourceNotFoundException , куда мы хотим отправить NOT_FOUND notification в ответном сообщении.
контроллер / контроллер
package com.bezkoder.spring.exhandling.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.bezkoder.spring.exhandling.exception.ResourceNotFoundException;
import com.bezkoder.spring.exhandling.model.Tutorial;
import com.bezkoder.spring.exhandling.repository.TutorialRepository;
@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public ResponseEntity> getAllTutorials(@RequestParam(required = false) String title) {
List tutorials = new ArrayList();
if (title == null)
tutorialRepository.findAll().forEach(tutorials::add);
else
tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
}
@GetMapping("/tutorials/{id}")
public ResponseEntity getTutorialById(@PathVariable("id") long id) {
Tutorial tutorial = tutorialRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));
return new ResponseEntity<>(tutorial, HttpStatus.OK);
}
@PostMapping("/tutorials")
public ResponseEntity createTutorial(@RequestBody Tutorial tutorial) {
Tutorial _tutorial = tutorialRepository.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false));
return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
}
@PutMapping("/tutorials/{id}")
public ResponseEntity updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
Tutorial _tutorial = tutorialRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));
_tutorial.setTitle(tutorial.getTitle());
_tutorial.setDescription(tutorial.getDescription());
_tutorial.setPublished(tutorial.isPublished());
return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
}
@DeleteMapping("/tutorials/{id}")
public ResponseEntity deleteTutorial(@PathVariable("id") long id) {
tutorialRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@DeleteMapping("/tutorials")
public ResponseEntity deleteAllTutorials() {
tutorialRepository.deleteAll();
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@GetMapping("/tutorials/published")
public ResponseEntity> findByPublished() {
List tutorials = tutorialRepository.findByPublished(true);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
}
}
Запуск и тестирование
Мы завершаем внедрение CRUD REST API и обработку исключений для него. Запустите приложение Spring Boot с помощью команды: mvn spring-boot:run .
- Получите несуществующий учебник:
- Обновите несуществующий учебник:
- Создать учебное пособие с неправильными полями:
- Удалить несуществующий учебник:
Вывод
Сегодня мы создали класс обработки исключений для Spring Boot Rest API, используя @ControllerAdvice и @ExceptionHandler . Теперь вы можете легко создать свой собственный пользовательский класс обработчика исключений или обрабатывать глобальное исключение в одном месте.
Если вы хотите добавить разбивку на страницы в этот проект Spring, вы можете найти инструкцию по адресу: Пример разбивки на страницы и фильтрации Spring Boot | Spring JPA, Доступный для просмотра по страницам
Для сортировки/упорядочения по нескольким полям: Spring Data JPA Сортировка/Упорядочение по нескольким столбцам | Spring Boot
Или способ написания модульного теста для репозитория JPA: Модульный тест Spring Boot для репозитория JPA с помощью @DataJpaTest
Вы также можете узнать, как развернуть это приложение Spring Boot на AWS (бесплатно) с помощью этого руководства . Или Dockerize с помощью Docker Compose: Пример Spring Boot и MySQL
Счастливого обучения! Еще увидимся.
Дальнейшее Чтение
Связанные Сообщения:
- Весенняя загрузка, Spring Data JPA – Пример Rest CRUD API
- Пример разбивки на страницы и фильтрации Spring Boot
- Сортировка/Порядок загрузки Spring по нескольким столбцам
Больше Практики:
- Пример загрузки составного файла Spring Boot
- Аутентификация на основе токена Spring Boot с помощью Spring Security и JWT
исходный код
Вы можете найти полный исходный код этого руководства по адресу Github .
Оригинал: “https://dev.to/tienbku/spring-boot-exception-handling-example-241c”