Benutzerdefinierte Behandlung von Fehlernachrichten für die REST-API
1. Überblick
In diesem Tutorial wird erläutert, wie Sie einen globalen Fehlerbehandler für eine Spring REST-API implementieren.
Wir werden die Semantik jeder Ausnahme verwenden, um aussagekräftige Fehlermeldungen für den Client zu erstellen, mit dem klaren Ziel, dem Client alle Informationen zur einfachen Diagnose des Problems bereitzustellen.
Beginnen wir mit der Implementierung einer einfachen Struktur zum Senden von Fehlern über das Kabel -ApiError:
public class ApiError {
private HttpStatus status;
private String message;
private List errors;
public ApiError(HttpStatus status, String message, List errors) {
super();
this.status = status;
this.message = message;
this.errors = errors;
}
public ApiError(HttpStatus status, String message, String error) {
super();
this.status = status;
this.message = message;
errors = Arrays.asList(error);
}
}
Die Informationen hier sollten einfach sein:
status: Der HTTP-Statuscode
message: Die mit der Ausnahme verbundene Fehlermeldung
error: Liste der erstellten Fehlermeldungen
Und für die eigentliche Ausnahmehandlungslogik im Frühjahr istwe’ll usedie Anmerkung von@ControllerAdvice:
@ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
...
}
3. Behandeln Sie Ausnahmen für fehlerhafte Anforderungen
3.1. Umgang mit den Ausnahmen
Lassen Sie uns nun sehen, wie wir mit den häufigsten Clientfehlern umgehen können - im Grunde genommen haben Szenarien eines Clients eine ungültige Anforderung an die API gesendet:
BindException: Diese Ausnahme wird ausgelöst, wenn schwerwiegende Bindungsfehler auftreten.
MethodArgumentNotValidException: Diese Ausnahme wird ausgelöst, wenn das mit@Valid kommentierte Argument die Validierung fehlschlägt:
@Override
protected ResponseEntity
Wie Sie sehen können,we are overriding a base method out of the ResponseEntityExceptionHandler and providing our own custom implementation.
Dies wird nicht immer der Fall sein. Manchmal müssen wir eine benutzerdefinierte Ausnahme behandeln, für die es keine Standardimplementierung in der Basisklasse gibt, wie wir später hier sehen werden.
Nächster:
MissingServletRequestPartException: Diese Ausnahme wird ausgelöst, wenn der Teil einer mehrteiligen Anforderung nicht gefunden wurde
MissingServletRequestParameterException: Diese Ausnahme wird ausgelöst, wenn der Parameter für die Anforderung fehlt:
@Override
protected ResponseEntity
ConstrainViolationException: Diese Ausnahme meldet das Ergebnis von Einschränkungsverletzungen:
@ExceptionHandler({ ConstraintViolationException.class })
public ResponseEntity handleConstraintViolation(
ConstraintViolationException ex, WebRequest request) {
List errors = new ArrayList();
for (ConstraintViolation> violation : ex.getConstraintViolations()) {
errors.add(violation.getRootBeanClass().getName() + " " +
violation.getPropertyPath() + ": " + violation.getMessage());
}
ApiError apiError =
new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
return new ResponseEntity(
apiError, new HttpHeaders(), apiError.getStatus());
}
TypeMismatchException: Diese Ausnahme wird ausgelöst, wenn versucht wird, die Bean-Eigenschaft mit einem falschen Typ festzulegen.
MethodArgumentTypeMismatchException: Diese Ausnahme wird ausgelöst, wenn das Methodenargument nicht der erwartete Typ ist:
@ExceptionHandler({ MethodArgumentTypeMismatchException.class })
public ResponseEntity handleMethodArgumentTypeMismatch(
MethodArgumentTypeMismatchException ex, WebRequest request) {
String error =
ex.getName() + " should be of type " + ex.getRequiredType().getName();
ApiError apiError =
new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
return new ResponseEntity(
apiError, new HttpHeaders(), apiError.getStatus());
}
3.2. Konsumieren der API vom Client
Schauen wir uns nun einen Test an, der aufMethodArgumentTypeMismatchException stößt: Wir werdensend a request with id as String instead of long:
@Test
public void whenMethodArgumentMismatch_thenBadRequest() {
Response response = givenAuth().get(URL_PREFIX + "/api/foos/ccc");
ApiError error = response.as(ApiError.class);
assertEquals(HttpStatus.BAD_REQUEST, error.getStatus());
assertEquals(1, error.getErrors().size());
assertTrue(error.getErrors().get(0).contains("should be of type"));
}
Und schließlich - unter Berücksichtigung der gleichen Anfrage:
Request method: GET
Request path: http://localhost:8080/spring-security-rest/api/foos/ccc
So sieht diese Art von JSON-Fehlerantwort aus:
{
"status": "BAD_REQUEST",
"message":
"Failed to convert value of type [java.lang.String]
to required type [java.lang.Long]; nested exception
is java.lang.NumberFormatException: For input string: \"ccc\"",
"errors": [
"id should be of type java.lang.Long"
]
}
4. BehandleNoHandlerFoundException
Als nächstes können wir unser Servlet anpassen, um diese Ausnahme auszulösen, anstatt eine 404-Antwort zu senden - wie folgt:
api
org.springframework.web.servlet.DispatcherServletthrowExceptionIfNoHandlerFoundtrue
Sobald dies passiert ist, können wir es einfach wie jede andere Ausnahme behandeln:
@Override
protected ResponseEntity handleNoHandlerFoundException(
NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String error = "No handler found for " + ex.getHttpMethod() + " " + ex.getRequestURL();
ApiError apiError = new ApiError(HttpStatus.NOT_FOUND, ex.getLocalizedMessage(), error);
return new ResponseEntity(apiError, new HttpHeaders(), apiError.getStatus());
}
{
"status":"NOT_FOUND",
"message":"No handler found for DELETE /spring-security-rest/api/xx",
"errors":[
"No handler found for DELETE /spring-security-rest/api/xx"
]
}
5. BehandleHttpRequestMethodNotSupportedException
Schauen wir uns als nächstes eine weitere interessante Ausnahme an -HttpRequestMethodNotSupportedException -, die auftritt, wenn Sie eine Anforderung mit einer nicht unterstützten HTTP-Methode senden:
@Override
protected ResponseEntity handleHttpRequestMethodNotSupported(
HttpRequestMethodNotSupportedException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getMethod());
builder.append(
" method is not supported for this request. Supported methods are ");
ex.getSupportedHttpMethods().forEach(t -> builder.append(t + " "));
ApiError apiError = new ApiError(HttpStatus.METHOD_NOT_ALLOWED,
ex.getLocalizedMessage(), builder.toString());
return new ResponseEntity(
apiError, new HttpHeaders(), apiError.getStatus());
}
Hier ist ein einfacher Test, der diese Ausnahme reproduziert:
{
"status":"METHOD_NOT_ALLOWED",
"message":"Request method 'DELETE' not supported",
"errors":[
"DELETE method is not supported for this request. Supported methods are GET "
]
}
6. BehandleHttpMediaTypeNotSupportedException
Behandeln wir nunHttpMediaTypeNotSupportedException - was auftritt, wenn der Client eine Anforderung mit nicht unterstütztem Medientyp sendet - wie folgt:
@Override
protected ResponseEntity handleHttpMediaTypeNotSupported(
HttpMediaTypeNotSupportedException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getContentType());
builder.append(" media type is not supported. Supported media types are ");
ex.getSupportedMediaTypes().forEach(t -> builder.append(t + ", "));
ApiError apiError = new ApiError(HttpStatus.UNSUPPORTED_MEDIA_TYPE,
ex.getLocalizedMessage(), builder.substring(0, builder.length() - 2));
return new ResponseEntity(
apiError, new HttpHeaders(), apiError.getStatus());
}
Hier ist ein einfacher Test, der auf dieses Problem stößt:
@Test
public void whenSendInvalidHttpMediaType_thenUnsupportedMediaType() {
Response response = givenAuth().body("").post(URL_PREFIX + "/api/foos");
ApiError error = response.as(ApiError.class);
assertEquals(HttpStatus.UNSUPPORTED_MEDIA_TYPE, error.getStatus());
assertEquals(1, error.getErrors().size());
assertTrue(error.getErrors().get(0).contains("media type is not supported"));
}
Zum Schluss - hier eine Beispielanfrage:
Request method: POST
Request path: http://localhost:8080/spring-security-
Headers: Content-Type=text/plain; charset=ISO-8859-1
Undthe error JSON response:
{
"status":"UNSUPPORTED_MEDIA_TYPE",
"message":"Content type 'text/plain;charset=ISO-8859-1' not supported",
"errors":["text/plain;charset=ISO-8859-1 media type is not supported.
Supported media types are text/xml
application/x-www-form-urlencoded
application/*+xml
application/json;charset=UTF-8
application/*+json;charset=UTF-8 */"
]
}
7. Standardhandler
Lassen Sie uns abschließend einen Fallback-Handler implementieren - eine Sammellogik, die alle anderen Ausnahmen behandelt, für die es keine spezifischen Handler gibt:
@ExceptionHandler({ Exception.class })
public ResponseEntity handleAll(Exception ex, WebRequest request) {
ApiError apiError = new ApiError(
HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
return new ResponseEntity(
apiError, new HttpHeaders(), apiError.getStatus());
}
8. Fazit
Das Erstellen eines ausgereiften Fehlerhandlers für eine Spring-REST-API ist schwierig und definitiv ein iterativer Prozess. Hoffentlich ist dieses Tutorial ein guter Ausgangspunkt, um dies für Ihre API zu tun, und ein guter Anker dafür, wie Sie den Kunden Ihrer API dabei helfen sollten, Fehler schnell und einfach zu diagnostizieren und an ihnen vorbeizugehen.
Diefull implementation dieses Tutorials finden Sie inthe github project - dies ist ein Eclipse-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.