Типичная Обработка Исключений В Java ☕ ️
В Java принято пытаться перехватить части нашего кода, которые по какой-то причине терпят неудачу
- Отсутствующие файлы, поврежденные данные и т.д…
try{ buggyMethod(); return "Done!"; }catch (RuntimeException e){ return "An error happened!"; }
Обработка Исключений Весной 🍃
Давайте рассмотрим рабочий процесс Spring как веб-фреймворк:
- Выслушивайте запросы клиента.
- Выполните некоторые действия, основанные на нашей бизнес-логике.
- Верните клиенту ответ, содержащий результат нашей работы.
Теперь мы в идеале хотим перехватить любое исключение (ошибку), которое может возникнуть на уровне 2 (принятие мер). Мы можем написать блок try catch для каждого метода контроллера, который обрабатывает исключения стандартным способом 🙌 🏽
@RestController @RequiredArgsConstructor public class TestController { private final ExceptionHandler exceptionHandler; @GetMapping("/test1") public void test1(){ try{ // test 1 things }catch (Exception e){ exceptionHandler.handleException(e); } } @GetMapping("/test2") public void test2(){ try{ // test 2 things }catch (Exception e){ exceptionHandler.handleException(e); } } }
👎 🏽 Проблема с этим подходом, однако, заключается в том, что он становится довольно утомительным, когда у нас есть еще много методов контроллера.
Зачем фиксировать все исключения? и не просто позволить им произойти? 🤷🏼
- Мы хотим, чтобы наше приложение было удобным для пользователя и обрабатывало все крайние случаи, поэтому мы хотим, чтобы оно возвращало ответы в стандартном формате.
- Мы также можем захотеть занести эти исключения в список невыполненных работ, чтобы вернуться к ним и исследовать их, или делать с ними все, что нам заблагорассудится.
@ControllerAdvice Спешит На Помощь 💪🏾
Идея состоит в том, что мы объявляем метод, который будет обрабатывать любые необработанные исключения в приложении.
Как это сделать? 👀
Во-первых, нам нужно объявить класс и аннотировать его с помощью @ControllerAdvice
. Затем мы объявляем методы, каждый из которых обрабатывает класс исключений.
@ControllerAdvice @Slf4j public class GlobalErrorHandler { @ResponseStatus(INTERNAL_SERVER_ERROR) @ResponseBody @ExceptionHandler(Exception.class) public String methodArgumentNotValidException(Exception ex) { // you can take actions based on the exception log.error("An unexpected error has happened", ex); return "An internal error has happened, please report the incident"; } @ResponseStatus(BAD_REQUEST) @ResponseBody @ExceptionHandler(InvalidParameterException.class) public String invalidParameterException(InvalidParameterException ex){ return "This is a BAD REQUEST"; } }
Что делает приведенный выше код? ☝️
- Объявляет два метода, которые будут выполняться всякий раз, когда возникает исключение класса
Исключение
,Исключение InvalidParameterException
(или их подкласс) выбрасывается и не обрабатывается локально в их потоке выполнения. - Они возвращают клиенту строковый ответ.
Обратите внимание, что мы можем указать более одного обработчика в классе с аннотацией @ControllerAdvice
.
Теперь давайте закодируем некоторые конечные точки для проверки. Давайте закодируем три конечные точки
- Тот, который обрабатывает выданное исключение.
- Два других оставляют обработку глобальному обработчику исключений
@RestController @RequiredArgsConstructor public class TestController { @GetMapping("/buggyMethod") public String testMeWithExceptionHandler(){ try{ buggyMethod(); return "Done!"; }catch (RuntimeException e){ return "An error happened!"; } } @GetMapping("/potentialBuggyMethod") public String testMeWithoutExceptionHandler(){ undercoverBuggyMethod(); return "Done!"; } @PostMapping("/invalidParamMethod") public String testForInvalidParam(){ buggyParameters(); return "Done"; } private void buggyMethod(){ throw new RuntimeException(); } private void undercoverBuggyMethod(){ throw new RuntimeException("oops"); } private void buggyParameters(){ throw new InvalidParameterException(); } }
Давайте Проверим Это С Помощью Некоторых Тестов 🧠
@WebMvcTest(controllers = TestController.class) public class GlobalExceptionHandlerTest { @Autowired private MockMvc mockMvc; @Test public void givenAGetRequestToBuggyEndPoint_DetectErrorMessage() throws Exception { MvcResult mvcResult = mockMvc .perform(get("/buggyMethod")) .andExpect(status().isOk()) .andReturn(); String response = mvcResult.getResponse().getContentAsString(); assertEquals(response, "An error happened!"); } @Test public void givenAGetRequestToPotentialBuggyMethod_DetectErrorMessage() throws Exception { MvcResult mvcResult = mockMvc .perform(get("/potentialBuggyMethod")) .andExpect(status().is5xxServerError()) .andReturn(); String response = mvcResult.getResponse().getContentAsString(); assertEquals(response, "An internal error has happened, please report the incident"); } @Test public void givenAPostRequestToBuggyMethod_DetectInvalidParameterErrorMessage() throws Exception { MvcResult mvcResult = mockMvc .perform(post("/invalidParamMethod")) .andExpect(status().isBadRequest()) .andReturn(); String response = mvcResult.getResponse().getContentAsString(); assertEquals(response, "This is a BAD REQUEST"); } }
Вывод 👈
Неожиданные и общие ошибки должны обрабатываться элегантно, чтобы обеспечить бесперебойную работу наших клиентов приложений. Лучше всего это сделать с помощью Spring ControllerAdvice .
Ознакомьтесь с этой статьей для получения более подробной информации об обработке ошибок для REST с помощью Spring 👈
Проверьте код на GitHub🥷
Оригинал: “https://dev.to/jarjanazy/handle-spring-exceptions-like-a-pro-1m5e”