2016-12-16 6 views
3

Spring 3.2.15, API REST на основе MVC здесь (не Весенняя загрузка, к сожалению!). Я пытаюсь реализовать исключение сопоставителя/обработчик, который отвечает следующим критериям:Spring MV 3.2 Отображение ответов на исключение

  • Независимо от того, что происходит (успех или ошибка), то Spring приложение всегда возвращает ответ объект из MyAppResponse (смотри ниже); и
  • В случае успешной обработки запроса вернуть HTTP-статус 200 (типичный); и
  • В случае обработки запроса и исключение происходит, мне нужно контролировать отображение конкретного исключения кодом состояния частности HTTP
    • Spring MVC рамки ошибок (таких, как BlahException) необходимо сопоставить с HTTP 422
    • исключения пользовательских приложений, таких, как мой FizzBuzzException имеют свои собственные схемы отображения состояния:
      • FizzBuzzException -> HTTP 401
      • FooBarException -> HTTP 403
      • OmgException -> HTTP 404
    • Все другие исключения, то есть, не пружинные исключения и без пользовательских исключений приложения (3 перечислены выше), должны производить HTTP 500

Где MyAppResponse объект:

// Groovy pseudo-code 
@Canonical 
class MyAppResponse { 
    String detail 
    String randomNumber 
} 

это появляется как ResponseEntityExceptionHandler мог бы сделать это для меня, но я не вижу лес через деревья w.r.t. как он получает переданные аргументы. Я надеюсь, что я могу сделать что-то вроде:

// Groovy-pseudo code 
@ControllerAdvice 
class MyAppExceptionMapper extends ResponseEntityExceptionHandler { 
    ResponseEntity<Object> handleFizzBuzzException(FizzBuzzException fbEx, HttpHeaders headers, HttpStatus status) { 
     // TODO: How to reset status to 401? 
     status = ??? 

     new ResponseEntity(fbEx.message, headers, status) 
    } 

    ResponseEntity<Object> handleFooBarException(FooBarException fbEx, HttpHeaders headers, HttpStatus status) { 
     // TODO: How to reset status to 403? 
     status = ??? 

     new ResponseEntity(fbEx.message, headers, status) 
    } 

    ResponseEntity<Object> handleOmgException(OmgException omgEx, HttpHeaders headers, HttpStatus status) { 
     // TODO: How to reset status to 404? 
     status = ??? 

     new ResponseEntity(omgEx.message, headers, status) 
    } 

    // Now map all Spring-generated exceptions to 422 
    ResponseEntity<Object> handleAllSpringExceptions(SpringException springEx, HttpHeaders headers, HttpStatus status) { 
     // TODO: How to reset status to 422? 
     status = ??? 

     new ResponseEntity(springEx.message, headers, status) 
    } 

    // Everything else is a 500... 
    ResponseEntity<Object> handleAllOtherExceptions(Exception ex, HttpHeaders headers, HttpStatus status) { 
     // TODO: How to reset status to 500? 
     status = ??? 

     new ResponseEntity("Whoops, something happened. Lol.", headers, status) 
    } 
} 

Любой идеи, как я могу в полной мере реализовать это отображение логику и требование сущности быть MyAppResponse экземпляром, а не просто строка?

Затем комментирует класс с @ControllerAdvice Единственное, что мне нужно сделать, чтобы настроить Spring на его использование?

ответ

1

Чтобы уменьшить ответ @ облигаций ява-облигаций вам не нужно строить ResponseEntity сами:

  1. Использование @ResponseStatus для каждого handleSomeException метода (например @ResponseStatus(HttpStatus.UNAUTHORIZED))
  2. Возврат таможенных пошлин MyAppResponse по этим методам

Но если каждый вид исключений будет обрабатываться одинаково только статус HTTP) Я предлагаю уменьшить MyAppExceptionMapper так:

@ControllerAdvice 
public class MyAppExceptionMapper { 
    private final Map<Class<?>, HttpStatus> map; 
    { 
     map = new HashMap<>(); 
     map.put(FizzBuzzException.class, HttpStatus.UNAUTHORIZED); 
     map.put(FooBarException.class, HttpStatus.FORBIDDEN); 
     map.put(NoSuchRequestHandlingMethodException.class, HttpStatus.UNPROCESSABLE_ENTITY); 
     /* List Spring specific exceptions here as @bond-java-bond suggested */ 
     map.put(Exception.class, HttpStatus.INTERNAL_SERVER_ERROR); 
    } 

    // Handle all exceptions 
    @ExceptionHandler(Exception.class) 
    @ResponseBody 
    public ResponseEntity<MyAppResponse> handleException(Exception exception) { 
     MyAppResponse response = new MyAppResponse(); 
     // Fill response with details 

     HttpStatus status = map.get(exception.getClass()); 
     if (status == null) { 
      status = map.get(Exception.class);// By default 
     } 

     return new ResponseEntity<>(response, status); 
    } 
} 

Pros:

  1. Довольно короткая.
  2. Отсутствие дублирования кода.
  3. Немного эффективнее.
  4. Прост в распространении.

Кроме того, вы можете перемещать конфигурацию отображения снаружи и вводить ее.

Как настроить MVC Dispatcher Servlet

Прежде всего, проверьте mvc-dispatcher-servlet.xml (или другой contextConfigLocation из web.xml) содержит:

<context:component-scan base-package="base.package"/> 
<mvc:annotation-driven/> 

Во-вторых, проверьте, если @ControllerAdvice аннотированный класс и @Controller аннотированный класс принадлежат к подпакету base.package.

Для получения более подробной информации см. Полные примеры на Exception Handling in Spring MVC или Spring MVC @ExceptionHandler Example.

+0

Спасибо @ Gregory.K (+1) - см. Мой комментарий под ответом Бонда. У меня к тебе тот же вопрос! Еще раз спасибо! – smeeb

+0

@smeeb, я только что добавил «Как настроить сервлет MVC Dispatcher». –

1

Сначала обработчик ошибок/исключений должен не беспокоиться об успешном ответе.(простой или REST контроллер) Метод

Таким образом, ответственность ответ успеха должна лежать контроллер (ы) с аннотацией @RequestMapping, как показано ниже

@RequestMapping(value = "/demo", method = RequestMethod.GET) 
@ResponseStatus(value = HttpStatus.OK) 
public MyAppResponse doSomething() { .... } 

Для отображения конкретного кода ответа HTTP, с исключением (с) просто написать @ControllerAdvice, как показано ниже (без дополнительной конфигурации не требуется)

@ControllerAdvice 
public class CustomExceptionHandler { 

    // Handle FizzBuzzException with status code as 401 
    @ExceptionHandler(value = FizzBuzzException.class) 
    @ResponseBody 
    public ResponseEntity<MyAppResponse> handleException(FizzBuzzException ex) { 
     return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNAUTHORIZED); 
    } 

    // Handle FooBarException with status code as 403 
    @ExceptionHandler(value = FooBarException.class) 
    @ResponseBody 
    public ResponseEntity<MyAppResponse> handleException(FooBarException ex) { 
     return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.FORBIDDEN); 
    } 

    // Handle OmgException with status code as 404 
    @ExceptionHandler(value = OmgException.class) 
    @ResponseBody 
    public ResponseEntity<MyAppResponse> handleException(OmgException ex) { 
     return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.NOT_FOUND); 
    } 

    // handle Spring MVC specific exceptions with status code 422 
    @ExceptionHandler(value = {NoSuchRequestHandlingMethodException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class, 
     HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class, 
     ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MethodArgumentNotValidException.class, 
     MissingServletRequestPartException.class, BindException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class}) 
    @ResponseBody 
    public ResponseEntity<MyAppResponse> handleException(Exception ex) { 
     return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNPROCESSABLE_ENTITY); 
    } 

    // Handle rest of the exception(s) with status code as 500 
    @ExceptionHandler(value = Exception.class) 
    @ResponseBody 
    public ResponseEntity<MyAppResponse> handleException(Exception ex) { 
     return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.INTERNAL_SERVER_ERROR); 
    } 

    private MyAppResponse buildResponse(Throwable t) { 
     MyAppResponse response = new MyAppResponse(); 
     // supply value to response object 
     return response; 
    } 
} 

Ля t знать в комментариях, если требуется какая-либо дополнительная информация.

P.S.: Список Spring MVC исключение reference

+0

Спасибо @Bond - Java Bond (+1) - однако либо Spring, похоже, не нахожу моего обработчика (и рассматривая его как '@ ControllerAdvice'), либо Tomcat как-то сбивает вещи (это Spring MVC REST сервис, развернутый как WAR для Tomcat 7). Если я hardcode 'throw new FizzBuzzException()' в методе контроллера, объект/тело ответа HTTP является HTML, который Tomcat отправляет обратно. Внутри этого HTML-кода находится 'FizzBuzzException' stacktrace ... но это не то, что мне нужно/нужно. Мне нужен HTTP-запрос для сериализации JSON, представляющий мой класс MyAppResponse.Нужно ли мне что-либо делать в моем XML-сервлете? – smeeb