【问题标题】:Spring MV 3.2 Exception Response MappingSpring MVC 3.2 异常响应映射
【发布时间】:2016-12-16 19:10:15
【问题描述】:

Spring 3.2.15,此处是基于 MVC 的 REST API(不是 Spring Boot,很遗憾!)。我正在尝试实现满足以下条件的异常映射器/处理程序:

  • 无论发生什么(成功或错误),Spring 应用程序始终返回MyAppResponse 的响应实体(见下文);和
  • 在成功处理请求的情况下,返回 HTTP 状态 200(典型);和
  • 在处理请求发生异常时,我需要控制特定异常到特定HTTP状态码的映射
    • Spring MVC 框架错误(如BlahException)必须映射到 HTTP 422
    • 自定义app异常,比如我的FizzBuzzException有自己的状态映射方案:
      • FizzBuzzException -> HTTP 401
      • FooBarException -> HTTP 403
      • OmgException -> HTTP 404
    • 所有其他异常,即非 Spring 异常和非自定义应用程序异常(上面列出的 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 以使用它唯一需要做的事情吗?

【问题讨论】:

    标签: spring-mvc exception-handling httpresponse


    【解决方案1】:

    要减少@bond-java-bond 答案,您不需要自己构建ResponseEntity

    1. 为每个handleSomeException 方法使用@ResponseStatus(例如@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);
        }
    }
    

    优点:

    1. 很短。
    2. 没有代码重复。
    3. 稍微更有效。
    4. 易于扩展。

    另外,您可以将映射配置移到外部并注入。

    如何配置 MVC Dispatcher Servlet

    首先,检查mvc-dispatcher-servlet.xml(或来自web.xml 的另一个contextConfigLocation)是否包含:

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

    其次,检查@ControllerAdvice注解类和@Controller注解类是否都属于base.package的子包。

    有关详细信息,请参阅 Exception Handling in Spring MVCSpring MVC @ExceptionHandler Example 上的完整示例。

    【讨论】:

    • 感谢@Gregory.K (+1) - 请在 Bond 的回答下方查看我的评论。我有同样的问题要问你!再次感谢!
    • @smeeb,我刚刚添加了“如何配置 MVC Dispatcher Servlet”部分。
    【解决方案2】:

    首先错误/异常处理程序应该担心成功响应。

    因此,成功响应的责任应该在于使用@RequestMapping 注释的控制器(普通或 REST 控制器)方法,如下所示

    @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;
        }
    }
    

    如果需要任何进一步的信息,请在 cmets 中告知。

    P.S.:Spring MVC 异常列表reference

    【讨论】:

    • 感谢@Bond - Java Bond (+1) - 但是 Spring 似乎没有找到我的处理程序(并将其视为 @ControllerAdvice)或 Tomcat 以某种方式搞砸了(这是Spring MVC REST 服务作为 WAR 部署到 Tomcat 7)。如果我在控制器方法中硬编码throw new FizzBuzzException(),HTTP 响应实体/正文是 Tomcat 发回的 HTML。在那个 HTML 里面是 FizzBuzzException stacktrace ......但这不是我需要/想要的。我需要将 HTTP 响应序列化为代表我的 MyAppResponse 类的 JSON。我需要在我的 servlet XML 中做任何事情吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-14
    • 2017-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多