wake-up-neo.net

Benutzerdefinierte Fehlerantwort für Spring Boot 404 ReST

Ich verwende Spring Boot zum Hosten einer REST API. Anstelle der Standard-Fehlerantwort möchte ich immer eine JSON-Antwort senden, auch wenn ein Browser auf die URL zugreift und auch eine benutzerdefinierte Datenstruktur. 

Ich kann dies mit @ControllerAdvice und @ExceptionHandler für benutzerdefinierte Ausnahmen tun. Ich kann jedoch für Standard- und behandelte Fehler wie 404 und 401 keine guten Wege finden.

Gibt es gute Muster, wie das geht?

9
Markus

Der 404-Fehler wird von DispatcherServlet behandelt. Es gibt eine Eigenschaft throwExceptionIfNoHandlerFound, die Sie überschreiben können.

In der Application-Klasse können Sie eine neue Bean erstellen:

@Bean
DispatcherServlet dispatcherServlet () {
    DispatcherServlet ds = new DispatcherServlet();
    ds.setThrowExceptionIfNoHandlerFound(true);
    return ds;
}

... und fangen dann die NoHandlerFoundException-Ausnahme in ein 

@EnableWebMvc
@ControllerAdvice
public class GlobalControllerExceptionHandler {
    @ExceptionHandler
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorMessageResponse requestHandlingNoHandlerFound(final NoHandlerFoundException ex) {
        doSomething(LOG.debug("text to log"));
    }
}
3
maxie

Ich habe die Musterlösung bereitgestellt, wie die Antwort für den 404-Fall überschrieben wird. Die Lösung ist ziemlich einfach und ich gebe Beispielcode, aber Sie können weitere Informationen zum ursprünglichen Thread finden: Spring Boot Rest - Wie konfiguriere ich 404 - Ressource nicht gefunden

First : Definiere einen Controller, der Fehlerfälle verarbeitet und die Antwort überschreibt:

@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(value= HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorResponse requestHandlingNoHandlerFound() {
        return new ErrorResponse("custom_404", "message for 404 error code");
    }
}

Second : Sie müssen Spring mitteilen, dass im Fall von 404 eine Ausnahme ausgelöst werden soll (Handler konnte nicht aufgelöst werden):

@SpringBootApplication
@EnableWebMvc
public class Application {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
    }
}
3
Ilya Ovesnov

Sie können benutzerdefinierte ErrorPage Objekte hinzufügen, die der Fehlerseitendefinition in web.xml entsprechen. Spring Boot liefert ein Beispiel ...

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
    return new MyCustomizer();
}

// ...

private static class MyCustomizer implements EmbeddedServletContainerCustomizer {

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"));
        container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not-found.html"));
    }

}

EDIT: Obwohl ich denke, dass die oben beschriebene Methode funktionieren wird, wenn Sie die Fehlerseiten-Controller zum Rest machen, wäre eine noch einfachere Möglichkeit, eine benutzerdefinierte ErrorController wie die unten stehende aufzunehmen ...

@Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
    return new CustomErrorController(errorAttributes);
}

// ...

public class CustomErrorController extends BasicErrorController {

    public CustomErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    @Override
    @RequestMapping(value = "${error.path:/error}")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        ResponseEntity<Map<String, Object>> error = super.error(request);
        HttpStatus statusCode = error.getStatusCode();

        switch (statusCode) {
        case NOT_FOUND:
            return getMyCustomNotFoundResponseEntity(request);
        case UNAUTHORIZED:
            return getMyCustomUnauthorizedResponseEntity(request);
        default:
            return error;
        }
    }
}
1
hyness

Zusammenfassend alle Antworten und Kommentare, ich denke, der beste Weg, dies zu tun ist-

Zuerst sagen Sie dem Spring Boot, dass eine Ausnahme ausgelöst werden soll, wenn in application.properties kein Handler gefunden wird.

spring.mvc.throw-exception-if-no-handler-found=true

Behandeln Sie dann NoHandlerFoundException in Ihrer Anwendung. Ich gehe mit dem folgenden Weg um

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(NoHandlerFoundException.class)
    public void handleNotFoundError(HttpServletResponse response, NoHandlerFoundException ex) {
        ErrorDto errorDto = Errors.URL_NOT_FOUND.getErrorDto();
        logger.error("URL not found exception: " + ex.getRequestURL());
        prepareErrorResponse(response, HttpStatus.NOT_FOUND, errorDto);
    }
}
1
Emdadul Sawon

Ich hatte das gleiche Problem, aber es wurde mit einer anderen Methode behoben. Um 404 , 401 und einen anderen Status in einer benutzerdefinierten Antwort zurückzugeben, können Sie dem Benutzer jetzt den Antwortstatus hinzufügen Ausnahmeklasse und rufen Sie es von Ihrem Ausnahmebehandler auf.

Mit der Spring Utility-Klasse AnnotationUtils können Sie den Status aller definierten benutzerdefinierten Ausnahmen mit der findAnnotation-Methode abrufen. Der entsprechende Status wird mit der für die Ausnahmen definierten Anmerkung zurückgegeben, einschließlich nicht gefunden.

Hier ist mein @RestControllerAdvice

@RestControllerAdvice
public class MainExceptionHandler extends Throwable{

 @ExceptionHandler(BaseException.class)
 ResponseEntity<ExceptionErrorResponse> exceptionHandler(GeneralMainException  e)
 {
  ResponseStatus status = AnnotationUtils.findAnnotation(e.getClass(),ResponseStatus.class);
  if(status != null)
  {
    return new ResponseEntity<>(new ExceptionErrorResponse(e.getCode(),e.getMessage()),status.code());
  }
 }

CustomParamsException gibt ungültigen Anforderungsstatus zurück

@ResponseStatus(value= HttpStatus.BAD_REQUEST)
public class CustomParamsException extends BaseException {
    private static final String CODE = "400";
    public CustomParamsException(String message) {
        super(CODE, message);
    }
}

Details nicht gefunden, um nicht gefundenen Status zurückzugeben

@ResponseStatus(value= HttpStatus.NOT_FOUND)
public class DetailsNotException extends BaseException {
    private static final String CODE = "400";
    public DetailsNotException(String message) {
        super(CODE, message);
    }
}

Eine GeneralMainException zum Erweitern von Excetion

public class GeneralMainException extends Exception {
private String code;
private String message;

public GeneralMainException (String message) {
    super(message);
}

public GeneralMainException (String code, String message) {
    this.code = code;
    this.message = message;
}

public String getCode() {
    return code;
}

@Override
public String getMessage() {
    return message;
}
}

Sie können entscheiden, mit anderen Systemausnahmen umzugehen, indem Sie es in den Controller-Hinweis aufnehmen. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) ExceptionErrorResponse sysError(Exception e) { return new ExceptionErrorResponse(""1002", e.getMessage()); }

0
Adewagold

Es scheint, dass Sie eine entsprechend kommentierte Methode einführen müssen, z. Bei einem nicht unterstützten Medientyp (415) wird Folgendes angezeigt:

  @ExceptionHandler(MethodArgumentNotValidException)
  public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
    logger.error('Caught exception', e)
    def response = new ExceptionResponse(
            error: 'Validation error',
            exception: e.class.name,
            message: e.bindingResult.fieldErrors.collect { "'$it.field' $it.defaultMessage" }.join(', '),
            path: req.servletPath,
            status: BAD_REQUEST.value(),
            timestamp: currentTimeMillis()
    )
    new ResponseEntity<>(response, BAD_REQUEST)
  }

Dies kann jedoch nicht möglich sein, da 401 und 404 möglicherweise geworfen werden, bevor sie DispatcherServlet erreichen. In diesem Fall funktioniert ControllerAdvice nicht.

0
Opal

Siehe Spring Boot REST Dienstausnahmebehandlung . Es zeigt, wie man dem Dispatcherservlet mitteilt, dass es Ausnahmen für "keine Route gefunden" ausgeben soll und wie diese Ausnahmen abgefangen werden. Wir (der Ort, an dem ich arbeite) verwenden dies derzeit in der Produktion für unsere REST Services.

0
ogradyjd