26.04.20223 min

Abdulcelil CercenaziSoftware Development Engineer

Jak obsługiwać wyjątki w Spring na najwyższym poziomie

Sprawdź, jak obsługiwać wyjątki w Javie i Springu niczym profesjonalista oraz umożliwić klientom bezproblemowe korzystanie z aplikacji.

Jak obsługiwać wyjątki w Spring na najwyższym poziomie

Typowa obsługa wyjątków w Javie

W Javie często zdarza się, że próbujemy znaleźć fragmenty kodu, które z jakiegoś powodu mają się nie powieść

  • Brakujące pliki, uszkodzone dane itd...
try{  
    buggyMethod();  
    return "Done!";  
}catch (RuntimeException e){  
    return "An error happened!";  
}


Obsługa wyjątków w Springu 

Zobaczmy, jak działa Spring jako framework sieciowy:

  1. Odbieranie żądań klienta.
  2. Podejmowanie pewnych działań opartych na logice biznesowej.
  3. Zwracanie klientowi odpowiedzi zawierającej wynik naszej pracy.


Idealnie byłoby przechwycić każdy wyjątek (błąd), który może pojawić się na poziomie 2 (podejmowanie akcji). Możemy napisać blok try catch przy każdej metodzie kontrolera, który będzie obsługiwał wyjątki w standardowy sposób.

@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);  
      }  
    }
}


Problematyczne w tym podejściu jest jednak to, że staje się ono dość uciążliwe, gdy mamy do czynienia z większą liczbą metod kontrolera.


Po co wychwytywać wszystkie wyjątki

zamiast tylko pozwalać na ich występowanie?

  • Chcemy, aby nasza aplikacja była przyjazna dla użytkownika i radziła sobie ze wszystkimi przypadkami brzegowymi, stąd też zależy nam, aby zwracała odpowiedzi w standardowym formacie.
  • Możemy również chcieć rejestrować te wyjątki w backlogu, aby móc do nich później wrócić i móc je sprawdzić lub też zrobić z nimi cokolwiek innego.


@ControllerAdvice na ratunek

Polega to na tym, że deklarujemy metodę, która będzie obsługiwać wszelkie nieobsługiwane wyjątki w aplikacji.


Jak to zrobić? 

Po pierwsze, musimy zadeklarować klasę i opisać ją jako @ControllerAdvice. Następnie deklarujemy metody, z których każda obsługuje wyjątek określonej klasy.

@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";  
   }  
}


Co robi powyższy kod? 

  • Deklaruje dwie metody, które będą uruchamiane za każdym razem, gdy zostanie rzucony wyjątek klasy Exception, InvalidParameterException (lub ich podklasa), który nie zostanie obsłużony lokalnie w ich wątku wykonania.
  • Zwracają one klientowi odpowiedź string.


Zauważ, że w klasie z adnotacją @ControllerAdvice możemy określić więcej niż jeden handler.

Teraz zakodujmy trzy punkty końcowe, na podstawie których będziemy mogli przeprowadzać walidację. 

  • Jeden, który obsługuje rzucony wyjątek.
  • Pozostałe dwa pozostawiają obsługę wyjątków globalnej obsłudze wyjątków.
@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();  
    } 
}


Zweryfikujmy to za pomocą kilku testów

@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");  
    }  
}


Wnioski 

Nieoczekiwane i ogólnie występujące błędy powinny być obsługiwane w odpowiedni sposób, aby zapewnić bezproblemowe korzystanie z aplikacji przez jej klientów. Najlepiej jest to zrobić za pomocą narzędzia ControllerAdvice w Springu.


Sprawdź kod na GitHub


Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>