Типичная Обработка Исключений В 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”