2017-07-28 4 views
0

Verwenden von Spring Boot für Restful Web Services.Spring Boot - Globaler benutzerdefinierter Ausnahmebehandlungsmechanismus mit RestControllerAdvice

Es wird versucht, einen globalen benutzerdefinierten Ausnahmebehandlungsmechanismus einzurichten, der auf @RestControllerAdvice basiert, der bekannte, aber auch unbekannte Ausnahmen behandeln kann.

pom.xml

<parent> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-parent</artifactId> 
    <version>1.5.4.RELEASE</version> 
</parent> 

<properties> 
    <java.version>1.8</java.version> 
</properties> 

<repositories> 
    <repository> 
     <id>spring-releases</id> 
     <url>https://repo.spring.io/libs-release</url> 
    </repository> 
</repositories> 

<pluginRepositories> 
    <pluginRepository> 
     <id>spring-releases</id> 
     <url>https://repo.spring.io/libs-release</url> 
    </pluginRepository> 
</pluginRepositories> 

<dependencies> 
    <!-- Spring --> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-web</artifactId> 
    </dependency> 

    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-test</artifactId> 
     <scope>test</scope> 
    </dependency> 
</dependencies> 

GlobalControllerExceptionHandler:

@RestControllerAdvice 
public class GlobalControllerExceptionHandler { 

    private static final Logger LOG = Logger.getLogger(GlobalControllerExceptionHandler.class); 

    @ExceptionHandler(value = { ConstraintViolationException.class }) 
    @ResponseStatus(HttpStatus.BAD_REQUEST) 
    public ApiErrorResponse constraintViolationException(ConstraintViolationException ex) { 
     LOG.error(ex.getCause().toString()); 
     return new ApiErrorResponse(400, "Bad Request"); 
    } 

    @ExceptionHandler(value = { NoHandlerFoundException.class }) 
    @ResponseStatus(HttpStatus.NOT_FOUND) 
    public ApiErrorResponse noHandlerFoundException(Exception ex) { 
     LOG.error(ex.getCause().toString()); 
     return new ApiErrorResponse(404, "Resource Not Found"); 
    } 

    @ExceptionHandler(value = { Exception.class }) 
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 
    public ApiErrorResponse unknownException(Exception ex) { 
     LOG.error(ex.getCause().toString()); 
     return new ApiErrorResponse(500, "Internal Server Error"); 
    } 
} 

ApiErrorResponse:

public class ApiErrorResponse { 

    private int status; 
    private String message; 

    public ApiErrorResponse(int status, String message) { 
     this.status = status; 
     this.message = message; 
    } 

    public int getStatus() { 
     return status; 
    } 

    public String getMessage() { 
     return message; 
    } 

    @Override 
    public String toString() { 
     return new ToStringBuilder(this).append(status) 
             .append(message) 
             .toString(); 
    } 
} 

Das Problem dabei ist, wenn ich eine 3rd-Party-Bibliothek verwenden, um etwas zu tun, die Eine unbekannte Ausnahme könnte ein 404 sein, wird aber als 500 zurückgegeben!

z.B. Mit Elasticsearch mit einem unbekannten Index (absichtlich Art von Ausnahme zu sehen):

{ 
    "timestamp": 15, 
    "status": 500, 
    "error": "Internal Server Error", 
    "exception": "org.elasticsearch.client.ResponseException", 
    "message": "POST http://localhost:9200/fn3r4343/_search?pretty=true: HTTP/1.1 404 Not Found" 
     { 
     "error": { 
      "root_cause": [ 
       { 
        "type": "index_not_found_exception", 
        "reason": "no such index", 
        "resource.type": "index_or_alias", 
        "resource.id": "fn3r4343", 
        "index_uuid": "_na_", 
        "index": "fn3r4343" 
       } 
      ], 
      "type": "index_not_found_exception", 
      "reason": "nosuchindex", 
      "resource.type": "index_or_alias", 
      "resource.id": "fn3r4343", 
      "index_uuid": "_na_", 
      "index": "fn3r4343" 
     } 
     { "root_cause" : 
      [ 
       { 
        "type" :"index_not_found_exception", 
        "reason" : no such index", "resource.type" : "index_or_alias", 
        "resource.id" : "fn3r4343", 
        "index_uuid" : "_na_", 
        "index" : "fn3r4343" 
       } 
      ], 
      [ 
        { 
        "type" : "index_not_found_exception", 
        "reason" : "no such index", 
        "resource.type" : "index_or_alias", 
        "resource.id" : "fn3r4343", 
        "index_uuid" : "_na_", 
        "index" : "fn3r4343" 
       }, 
       "status": 404 
       ] 
     } 
     "path": "/myapp/search" 
} 

Wie man sehen kann, gibt diese als HTTP 500-Status, aber in der Nutzlast sein wirklich HTTP 404!

Was ich suche zurückzukehren, ist dies:

{ 
    "message" : "Index Not Found Exception", 
    "status" : "HTTP 404" 
} 

Und für bekannte HTTP 404 Ausnahmen:

Gibt es eine gute Praxis/Mechanismus RestControllerAdvice zu verwenden, jede Art zu fangen von Ausnahme und Anpassung der Antwort in ein JSON-Format, das für einen Client, der die REST-API verwendet, lesbar/nützlich ist?

Dieser Beitrag zu Elasticsearch nicht wirklich spezifisch ist, sondern sucht, wie durch den Versuch @RestControllerAdvice zu verwenden, jede Art von Ausnahme zu behandeln für eine Client-Anwendung die richtige Antwort setzen ...

Antwort

1

die zugrunde liegende Ausnahme, die ausgelöst wird, ist ein org.elasticsearch.client.ResponseException (Sie sind wahrscheinlich das Low-Level-REST-Client).

So in Ihrem Rat brauchen Sie für diese Ausnahme einen Handler hinzuzufügen und den zugrunde liegenden Statuscode zurück:

@ExceptionHandler(value = { ResponseException.class }) 
public ApiErrorResponse noHandlerFoundException(Exception ex) { 
    LOG.error(ex.getCause().toString()); 
    int status = ((ResponseException) ex).getResponse().getStatusLine().getStatusCode(); 
    return new ApiErrorResponse(status, "<some message depending on status code>"); 
} 
+0

Danke @Val ... Ich verwende den Low-Level-REST-Client. Ihre Lösung hat mir sehr geholfen, aber wie kann ich die eigentliche Exception-Nachricht erhalten? Übrigens, wir kennen uns von der Java Ranch, Prost! –

0

Ich denke, das unter Klasse helfen soll:

@ControllerAdvice 
public class CustomExceptionHandler extends ResponseEntityExceptionHandler { 


@ExceptionHandler(value = {MyException.class}) 
protected ResponseEntity<Object> handleConflict(Exception exception, WebRequest request) 
{ 
    FinalResponse response = new FinalResponse()//Define this Bean in a way that it contains all required paramteres to be sent in response when exception occurs 
    response.setErrorCd("123"); 
    response.setMessage("Exception while processing."); 
    if (exception instanceof MyException) 
    { 
     return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); 
    } 
    else 
    { 
     return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); 
    } 
} 
} 
+0

Danke, aber was, wenn die MyException.class zu einer 3rd-Party-lib gehörte (wie Elastic Suche)? Gibt es eine generische Möglichkeit, einen beliebigen Typ von Exception-Throw zu erhalten? Wo bekomme ich auch die tatsächliche Zahl für die response.setErrorId ("123")? Ich denke, die richtige Nummer zu finden, war die Schlüssellösung für meine Frage. Wie würde der aufrufende RestController dies verwenden (z. B. was würde für eine Web-Anfrage eingefügt werden)? –

+0

Wickeln Sie diese Drittanbieter-Ausnahme um, indem Sie in Ihrem Code try catch hinzufügen und eine eigene Ausnahme auslösen. Selbst wenn Sie diesen Schritt nicht tun, habe ich erwähnt, dass dies in ELSE Teil der von mir bereitgestellten Logik behandelt wird, aber das wird nicht informativ sein. Das Umbrechen der Drittanbieter-Ausnahmeklasse wird also besser helfen. Auch ich hatte erwähnt, definieren Sie Ihre eigene FinalResponse-Klasse, die Felder haben, die Sie als eine Ausgabe bereitstellen möchten, wenn dieser bestimmte Fehler auftritt.Sie sollten idealerweise Fehlercodes und entsprechende Nachrichten definieren, die mit einem Fehler reagieren. –