如何在Spring的ExceptionHandler中获取Body

目前是用SpringMVC时,往往使用ExceptionHandler去做Controller层的统一异常处理。
使用ExceptionHandler注解的异常处理方法可以使用很灵活的方法签名。


可使用的参数类型

  • 一个异常参数。声明一个一般性的异常或者更加具体的异常
  • Request 和/或 response 对象(Servlet API 或 Portlet API)。可以选择一个特定 - request/response的类型,比如ServletRequest / HttpServletRequest
  • Session 对象
  • WebRequest 或 NativeWebRequest
  • Locale
  • InputStream / Reader 访问请求内容
  • OutputStream / Writer 生成响应内容
  • Model

异常处理方法支持的返回值类型

  • ModelAndView 对象 (Servlet MVC or Portlet MVC)
  • Model 对象
  • Map 对象,
  • View 对象
  • 被解析成一个视图名称的String 值
  • @ResponseBody 注解的方法 (仅限Servlet) 设置响应内容
  • HttpEntity<?> 或 ResponseEntity<?> (仅限Servlet) 设置响应头和响应内容
  • void。方法自己处理了响应。

如何在异常发生时输出请求

发生异常时,不仅仅需要输出异常本身,经常还需要根据Request的具体内容来分析、排查问题。
比如HttpRequestMethodNotSupportedException、HttpMessageConversionException等等,这些异常发生在业务代码处理之前,业务代码是无法获取到request的数据的,发生异常时如果能够看到请求body的具体内容,那么处理起来就可以对症下药,事半功倍。
说起来简单,做起来却不是很顺当,虽然ExcelptionHandler中可以传入ServerletRequest作为入参,但是ServerletRequest的inputStream只能被读取一次,发生异常的时候再想去读取body只能悲催的得到一个已经Closed的Stream。
找了一大圈,发现了一个有效的方法,感谢StackOverflow -_-~

使用ContentCachingRequestWrapper

  1. 通过过滤器将ServerletRequest封装成ContentCachingRequestWrapper,body被读取后,会被它缓存。
@Component
   public class RequestWrapperFilter extends OncePerRequestFilter {

       @Override
       protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
           filterChain.doFilter(new ContentCachingRequestWrapper(httpServletRequest), httpServletResponse);
       }
   }
  1. ExceptionHandler传入ServletRequest,此时的ServletRequest就是ContentCachingRequestWrapper,输出即可

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class,
            HttpMessageConversionException.class,
            TypeMismatchException.class})
    public ResponseEntity<Response> returnMediaTypeNotSupportError(Exception ex, ServletRequest request) {
        if (request != null && request instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
            logger.warn("BAD_REQUEST_BODY:{}", StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
        }
           .....

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 74,836评论 12 116
  • 这部分主要是与Java Web和Web Service相关的面试题。 96、阐述Servlet和CGI的区别? 答...
    杂货铺老板阅读 497评论 0 8
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 77,498评论 25 510
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 28,535评论 10 316
  • 不知不觉间…我好像喜欢上了他 开始对他的一举一动都十分关注 就连他每次走过我的窗前 我都会抬起头 痴痴的望着他 看...
    旧颜难忘阅读 29评论 0 0