Spring MVC 全局异常处理-RESTAPI接口返回统一JSON格式-自定义异常处理--404异常捕捉

写之前大概两周草草的将一些代码保存在草稿箱,今天有空来看,结果都没有了【怨念】---重新整理一下了 -----【转载请标注出处

  • 第一部分:需求
  • 第二部分:实现方式
  • 第三部分:404异常捕捉不能实现分析
  • 第四部分:原因和源码分析
  • 第五部分:最终总结

需求

  • **本意是想针对对外REST接口的返回格式进行统一,结果404错误始终无法被捕捉 **

实现方式

对于目前的主流方式全部尝试了一遍,主要有三种,还有其他一些异类采用Filter之类的方式实现,如需可以自取

**1. SimpleMappingExceptionResolver **
采用xml配置方式,代码零入侵 。自由性不足,获取异常信息少(只有异常信息)。
2. HandlerExceptionResolver
自定义类实现该类。可以实现统一异常处理里。我在实现的时候却没有生效,而是走了其自定义的DefaultHandlerExceptionResolver,具体原因下面分析
3. @ExceptionHandler注解
这种捕捉方式要求和被捕捉Controller在同一个类中,一般实现方式是把ExceptionHandler放在BaseController中继承,自由性差。
4.@ControllerAdvice注解
这种需要配合@ExceptionHandler属于第三种方式变种,我使用的则是这种方式的变种,下面详解。

原因和源码分析

首先来分析异常的处理过程

上面是简化的请求流转图,感谢某位同学的图片。

下面是程序员赵鑫的原理分析图,我搬来一用。当然我没有授权,如果有争议,我援引互联网信息共享条例自护(黑人问号脸)

照例感谢程序员赵鑫同学,想看再详细的分析请转贴这里

上图是SpringMVC的异常处理器结构,HandlerExceptionResolver是一个接口,留给自定义的时候使用。

异常处理器的处理顺序是:异常->HandlerExceptionResolver->自定义异常->默认异常处理器

SpringMVC的异常处理器通过实现Orderd接口,定义Order数值为每个异常处理器排序,图中可以看到默认异常处理器都继承于AbstractHandlerExceptionResolver。看图:

默认异常处理器都实现了doResolverException()方法。

图上的每一个处理器其实都代表了可以实现全局自定义处理的一种或者多种方式。

404异常捕捉不能实现分析

在我实现全局处理异常的时候,发现404异常并没有被捕捉,参考多方资料后发现,SpringleMVC的机制默认是对404异常不进行抛出动作的,直接在Response中设置错误代码,直接返回。具体看图:

图中的属性 throwExceptionIfNoHandlerFound = false 就是404异常抛出错误与否的判断属性,下面是判断源码,throwExceptionIfNoHandlerFound为true则会抛出异常

原因和源码分析

下面对几种实现方式进行解析

  • 1.SimpleMappingExceptionResolver
    代码如下:这里不做详解,缺点是不灵活,获取信息参数有限
  <!-- 出现异常会跳到这个页面,总错误处理-->
 <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="defaultErrorView">
           <value>/error/error</value>
      </property>
      <property name="defaultStatusCode">
           <value>500</value>
      </property>
      <property name="warnLogCategory">
           <value>org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver</value>
      </property>
  <!-- 如果不设置exceptionMappings,就会全局异常捕获 -->
      <property name="exceptionMappings">
           <props>
                <prop key="Java.sql.SQLException">/error/error</prop>
                <prop key="Java.lang.RuntimeException">/error/error</prop>
                <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">/error/error</prop>
           </props>
      </property>
</bean>

  • 2.HandlerExceptionResolver
    这种实现方式会受到容器加载顺序影响(可能是这个原因),如果没有加载完全,会按照SpringMVC默认的配置文件中的处理顺序进行处理
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
     public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
          Map<String, Object> model = new HashMap<String, Object>();
          model.put("ex", ex);
  
          // 根据不同错误转向不同页面
          if(ex instanceof BusinessException) {
               return new ModelAndView("error-business", model);
          }else if(ex instanceof ParameterException) {
               return new ModelAndView("error-parameter", model);
          } else {
               return new ModelAndView("error", model);
          }
     }
}

这种需要在Spring的配置文件applicationContext.xml中增加以下内容:

//有一种说法,如果不起作用,id写成这样说不定能解决问题,因为加载的时候是按照这个id去加载的
< bean id="handlerExceptionResolver" class="cn.basttg.core.exception.MyExceptionHandler"/>  
  • 3.ControllerAdvice(和ExceptionHandler放在一起了)

@ControllerAdvice  
public class ExceptionHandler {  
  
    @ResponseBody  
    @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class//这里可以选择其他具体的异常)  
    public ResponseModel handleUnexpectedServerError(Exception ex) {  
        ex.printStackTrace();  
  
        // 处理异常  
        ResponseModel response = new ResponseModel();  
  
        String errorMsg = ex.getMessage();  
        response.setCode(Integer.valueOf(ResultCode.SYSTEM_EXCEPTION));  
        if(errorMsg.contains("excpStart")){  
            errorMsg = errorMsg.substring(errorMsg.indexOf("excpStart") + 9, errorMsg.indexOf("excpEnd"));  
        }  
        response.setMessage(errorMsg);  
  
        // 返回数据  
        return response;  
    }  
  • 4.ControllerAdvice(我的实现方式)
@ControllerAdvice
public class GlobalExceptionResolver extends DefaultHandlerExceptionResolver {

    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String url = request.getServletPath();
        if (url.startsWith("/api")) {//api返回异常拦截

            if (ex instanceof HttpRequestMethodNotSupportedException) {
                setResponseParam(response, 405, "请求方式错误!");
                return null;
            }

            if (ex instanceof MissingServletRequestParameterException) {
                setResponseParam(response, 400, "错误请求!");
                return null;
            }

            if (ex instanceof NoHandlerFoundException) {
                //可以进行其他方法处理,LOG或者什么详细记录,我这里直接返回JSON
                setResponseParam(response, 404, "请求路径错误!");
                return null;
            }

            setResponseParam(response, 500, "服务器内部错误!服务暂时不可用!");
            return null;
        }
        
        //这里调用父类的异常处理方法,实现其他不需要的异常交给SpringMVC处理
        return super.doResolveException(request, response, handler, ex);
        
    }

    private void setResponseParam(HttpServletResponse response, int code, String msg) throws IOException {
        JSONObject j = JSONObject.fromObject(R.error(code, msg));
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(j.toString());
    }
}

参考:

公子的专栏焰尾迭程序猿之洞Exception Handling in Spring MVC程序员赵鑫

最终总结

联系点这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,030评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,198评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,995评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,973评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,869评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,766评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,967评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,599评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,886评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,901评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,728评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,504评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,967评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,128评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,445评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,018评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,224评论 2 339