В этом руководстве мы рассмотрим пример обработки исключений 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 ResponseEntityresourceNotFoundException(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 ResponseEntityresourceNotFoundException(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”