Obsługa błędów - dla mnie ta fraza kojarzy się z wściekłymi programistami frontendowymi oskarżającymi zespół backendowy o rzucanie w eter wyjątków bez żadnego ujednoliconego formatu odpowiedzi.

Wyobraźmy sobie, że ktoś zawiódł podczas procesu rozwoju lub planowania, a teraz musimy to naprawić. Jak ujednolicić odpowiedzi na błędy za pomocą Spring Boot? Moim ulubionym jest użycie @ControllerAdvice. Jest to adnotacja używana do definiowania klasy, która zawiera @ExceptionHandler metody z adnotacjami, które definiują sposób obsługi wyjątków.

Niestandardowe odpowiedzi na błędy

Aby ujednolicić logikę, musimy się przygotować:

  1. Klasa treści odpowiedzi - używana w każdej odpowiedzi na błąd.
  2. Wspólna metoda zwracająca ten sam format dla każdego obsłużonego wyjątku.
  3. Klasy obsługi wyjątków definiujące sposób obsługi danych wyjątków.

Przygotowanie

Zakładając, że pracujemy nad błędami dla menedżera pracowników i mamy employees/{id} punkt końcowy, który rzuca EmployeeNotFoundException gdy pracownik nie istnieje, stwórzmy/dodajmy test integracji spock w EmployeesControllerTest:

    def "should respond with NOT_FOUND Http status code when user with given firstName doesn't exist"() {
        when:
        def resultingStatusCode
        def resultingBody
        try {
            restTemplate.getForEntity("http://localhost:$runningServerPort/employees/test-id", EmployeesDto)
        } catch (HttpStatusCodeException e) {
            resultingStatusCode = e.statusCode
            resultingBody = new JsonSlurper().parseText(e.getResponseBodyAsString())
        }

        then:
        resultingStatusCode == HttpStatus.NOT_FOUND
        with(resultingBody) {
            message == "Employee (id = 'test-id') doesn't exist."
            code == "employee_not_found"
            details == [[employeeId: "test-id"]]
        }
    }

Oczywiście test zakończy się niepowodzeniem, ponieważ odpowiedź jest zupełnie inna niż ta, której oczekujemy:

reakcja przed wiosną

obsługa błędów rozruchu sprężynowego

Treść odpowiedzi

Przede wszystkim musimy przygotować ciało wyjątkowej odpowiedzi. Nazwijmy go ErrorResponse i wprowadź nazwę główną projektu:

class ErrorResponse {
    private final String message;
    private final String code;          // code for frontend to tell which message it needs to show for user 
    private final List<Object> details; // ie. details which field in input failed
    
    // ... constructors, builders, getters, setters, equals, hashcode, toString ... but I prefer to use Lombok instead ;)
}

Czy nie sądzisz, że dobrze byłoby zdefiniować enum z kodami błędów, na wypadek gdyby inni programiści pracowali z tym kodem? Stwórzmy go:

enum ErrorCode {
   EMPLOYEE_NOT_FOUND("employee_not_found");

   private final String code;

   ErrorCode(String code) {
       this.code = code;
   }

   public String getCode() {
       return this.code;
   }
}

Wspólna odpowiedź

Następnie musimy utworzyć abstract class z metodą, która zdefiniuje odpowiedź. Gdzie? To zależy od projektu, ale prawdopodobnie w katalogu głównym projektu, ponieważ musimy uzyskać do niego dostęp z pakietu każdej funkcji.

public abstract class HttpResponseExceptionHandler {
    protected ResponseEntity<ErrorResponse> getErrorResponseEntity(
            Exception e,
            String errorCode,
            List<Object> details,
            HttpStatus status) {
        ErrorResponse errorResponse = new ErrorResponse(e.getMessage(), errorCode, details);
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        headers.set("Content-type", MediaType.APPLICATION_JSON_VALUE);
        return new ResponseEntity<>(errorResponse, headers, status);
    }
}

Obsługa wyjątków

Następną rzeczą jest przygotowanie @ControllerAdvice dla każdego pakietu funkcjonalności. Jest to nasze miejsce do definiowania obsługi wyjątków dla błędów funkcjonalności:

@ControllerAdvice
class EmployeeExceptionHandler extends HttpResponseExceptionHandler {
    
    @ExceptionHandler(value = {EmployeeNotFoundException.class})
    ResponseEntity<ErrorResponse> handleCustomerAlreadyExists(EmployeeNotFoundException e) {
        Map<String, String> detailsMap = Collections.singletonMap("employeeId", e.getEmployeeId());
        return getErrorResponseEntity(
                e,
                ErrorCode.EMPLOYEE_NOT_FOUND.getCode(),
                Collections.singletonList(detailsMap),
                HttpStatus.NOT_FOUND);
    }
}

Oczywiście w @ExceptionHandler's value można zdefiniować więcej klas wyjątków, jeśli pasują one do tego samego mapowania.

Weryfikacja

Uruchom wcześniej przygotowany test:

obsługa błędów spring boot

Teraz sprawdź odpowiedź:

obsługa błędów w rozruchu wiosennym

To działa!

Zastępowanie domyślnych odpowiedzi na błędy Spring

Prawdopodobnie zauważyłeś, że istnieją pewne domyślne wiosenne programy obsługi wyjątków. Zostały one stworzone w celu uproszczenia odpowiedzi i wychwycenia możliwych wyjątków, takich jak HttpRequestMethodNotSupportedException. Zastąpmy wspomniany.

Tradycyjnie, utwórz test:

    def "should override default spring response for NotSupportedMethodException"() {
        when:
        def resultingStatusCode
        def resultingBody
        try {
            restTemplate.put("http://localhost:$runningServerPort/employees/test-id", EmployeesDto)
        } catch (HttpStatusCodeException e) {
            resultingStatusCode = e.statusCode
            resultingBody = new JsonSlurper().parseText(e.getResponseBodyAsString())
        }

        then:
        resultingStatusCode == HttpStatus.METHOD_NOT_ALLOWED
        with(resultingBody) {
            message == "Not supported HTTP method. Available methods are: [GET]"
            code == "not_supported_http_method"
        }
    }

Obsługa błędów http w Spring Boot

Test kończy się niepowodzeniem, więc musimy nadpisać handleHttpRequestMethodNotSupported metoda z ResponseEntityExceptionHandler:

@ControllerAdvice
class SpringRESTExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
            HttpRequestMethodNotSupportedException ex,
            HttpHeaders headers,
            HttpStatus status,
            WebRequest request) {

        pageNotFoundLogger.warn(ex.getMessage());

        Set<HttpMethod> supportedMethods = ex.getSupportedHttpMethods();
        if (!CollectionUtils.isEmpty(supportedMethods)) {
            headers.setAllow(supportedMethods);
        }
        ErrorResponse errorResponse = new ErrorResponse(
                String.format("Not supported HTTP method. Available methods are: %s", supportedMethods),
                "not_supported_http_method",
                null);
        return handleExceptionInternal(ex, errorResponse, headers, HttpStatus.METHOD_NOT_ALLOWED, request);
    }
}

Oczywiście należy wymienić "not_supported_http_method" z niektórymi enum lub stały

Uruchom test:

typowa obsługa błędów http w spring boot

Sprawdźmy teraz żądanie:

sekunda po teście sprężyny

Wnioski

Jak widać, jest to naprawdę proste rozwiązanie. Możesz po prostu zastąpić domyślne odpowiedzi na błędy Spring, a także niestandardowe odpowiedzi na błędy wyjątków. Gdy format odpowiedzi i @ControllerAdvice klasy są zdefiniowane, jedyną rzeczą do zrobienia jest przygotowanie nowych metod obsługi.

Chcesz porozmawiać z naszymi ekspertami na temat tworzenia oprogramowania na zamówienie

P: Czym jest obsługa błędów w Spring Boot?

Obsługa błędów w Spring Boot odnosi się do procesu definiowania sposobu obsługi wyjątków, które mogą wystąpić w aplikacji Spring Boot. Można to zrobić za pomocą adnotacji @ControllerAdvice, aby zdefiniować klasę zawierającą metody z adnotacją @ExceptionHandler, które określają sposób obsługi określonych wyjątków.

P: Czym jest klasa ErrorResponse w obsłudze błędów Spring Boot?

Klasa ErrorResponse jest klasą używaną do definiowania treści wyjątkowej odpowiedzi w obsłudze błędów Spring Boot. Zazwyczaj zawiera ona takie właściwości jak komunikat, kod i szczegóły dotyczące błędu.

P: Czym jest klasa HttpResponseExceptionHandler w obsłudze błędów Spring Boot?

Klasa HttpResponseExceptionHandler jest klasą abstrakcyjną, która definiuje metodę tworzenia standardowej odpowiedzi na wyjątki w Spring Boot. Klasa ta może być rozszerzana przez inne klasy obsługi wyjątków w celu stworzenia spójnego formatu odpowiedzi na błędy.

5/5 - (7 głosów)