SpringMVC全局异常统一处理以及处理顺序
最近在使用SpringMVC做全局异常统一处理的时候遇到的问题,就是想把ajax请求和普通的网页请求分开返回json错误信息或者跳转到错误页。

在实际做的时候先按照标准的方式自定义一个HandlerExceptionResolver,命名为SpringHandlerExceptionResolver,实现HandlerExceptionResolver接口,重写resolveException方法,具体实现如下:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonJsonView;
import com.butioy.common.bean.JsonResult;
import com.butioy.common.exception.BaseSystemException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; /**
* <p>
* spring MVC 统一异常处理
* </p>
*
* @author butioy
*/
public class SpringHandlerExceptionResolver implements HandlerExceptionResolver { private static Logger logger = LoggerFactory.getLogger(SpringHandlerExceptionResolver.class); private FastJsonConfig fastJsonConfig; @Autowired
public SpringHandlerExceptionResolver(FastJsonConfig fastJsonConfig) {
this.fastJsonConfig = fastJsonConfig;
} @Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mv = specialExceptionResolve(ex, request);
if (null == mv) {
String message = "系统异常,请联系管理员";
//BaseSystemException是我自定义的异常基类,继承自RuntimeException
if (ex instanceof BaseSystemException) {
message = ex.getMessage();
}
mv = errorResult(message, "/error", request);
}
return mv;
} /**
* 这个方法是拷贝 {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#doResolveException},
* 加入自定义处理,实现对400, 404, 405, 406, 415, 500(参数问题导致), 503的处理
*
* @param ex 异常信息
* @param request 当前请求对象(用于判断当前请求是否为ajax请求)
* @return 视图模型对象
*/
private ModelAndView specialExceptionResolve(Exception ex, HttpServletRequest request) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException
|| ex instanceof NoHandlerFoundException) {
return result(HttpExceptionEnum.NOT_FOUND_EXCEPTION, request);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return result(HttpExceptionEnum.NOT_SUPPORTED_METHOD_EXCEPTION, request);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return result(HttpExceptionEnum.NOT_SUPPORTED_MEDIA_TYPE_EXCEPTION, request);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return result(HttpExceptionEnum.NOT_ACCEPTABLE_MEDIA_TYPE_EXCEPTION, request);
}
else if (ex instanceof MissingPathVariableException) {
return result(HttpExceptionEnum.NOT_SUPPORTED_METHOD_EXCEPTION, request);
}
else if (ex instanceof MissingServletRequestParameterException) {
return result(HttpExceptionEnum.MISSING_REQUEST_PARAMETER_EXCEPTION, request);
}
else if (ex instanceof ServletRequestBindingException) {
return result(HttpExceptionEnum.REQUEST_BINDING_EXCEPTION, request);
}
else if (ex instanceof ConversionNotSupportedException) {
return result(HttpExceptionEnum.NOT_SUPPORTED_CONVERSION_EXCEPTION, request);
}
else if (ex instanceof TypeMismatchException) {
return result(HttpExceptionEnum.TYPE_MISMATCH_EXCEPTION, request);
}
else if (ex instanceof HttpMessageNotReadableException) {
return result(HttpExceptionEnum.MESSAGE_NOT_READABLE_EXCEPTION, request);
}
else if (ex instanceof HttpMessageNotWritableException) {
return result(HttpExceptionEnum.MESSAGE_NOT_WRITABLE_EXCEPTION, request);
}
else if (ex instanceof MethodArgumentNotValidException) {
return result(HttpExceptionEnum.NOT_VALID_METHOD_ARGUMENT_EXCEPTION, request);
}
else if (ex instanceof MissingServletRequestPartException) {
return result(HttpExceptionEnum.MISSING_REQUEST_PART_EXCEPTION, request);
}
else if (ex instanceof BindException) {
return result(HttpExceptionEnum.BIND_EXCEPTION, request);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return result(HttpExceptionEnum.ASYNC_REQUEST_TIMEOUT_EXCEPTION, request);
}
} catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
} /**
* 判断是否ajax请求
*
* @param request 请求对象
* @return true:ajax请求 false:非ajax请求
*/
private boolean isAjax(HttpServletRequest request) {
return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
} /**
* 返回错误信息
*
* @param message 错误信息
* @param url 错误页url
* @param request 请求对象
* @return 模型视图对象
*/
private ModelAndView errorResult(String message, String url, HttpServletRequest request) {
logger.warn("请求处理失败,请求url=[{}], 失败原因 : {}", request.getRequestURI(), message);
if (isAjax(request)) {
return jsonResult(, message);
} else {
return normalResult(message, url);
}
} /**
* 返回异常信息
*
* @param httpException 异常信息
* @param request 请求对象
* @return 模型视图对象
*/
private ModelAndView result(HttpExceptionEnum httpException, HttpServletRequest request) {
logger.warn("请求处理失败,请求url=[{}], 失败原因 : {}", request.getRequestURI(), httpException.getMessage());
if (isAjax(request)) {
return jsonResult(httpException.getCode(), httpException.getMessage());
} else {
return normalResult(httpException.getMessage(), "/error");
}
} /**
* 返回错误页
*
* @param message 错误信息
* @param url 错误页url
* @return 模型视图对象
*/
private ModelAndView normalResult(String message, String url) {
Map<String, String> model = new HashMap<String, String>();
model.put("errorMessage", message);
return new ModelAndView(url, model);
} /**
* 返回错误数据
*
* @param message 错误信息
* @return 模型视图对象
*/
private ModelAndView jsonResult(int code, String message) {
ModelAndView mv = new ModelAndView();
FastJsonJsonView view = new FastJsonJsonView();
view.setFastJsonConfig(fastJsonConfig);
view.setAttributesMap((JSONObject) JSON.toJSON(JsonResult.fail(code, message)));
mv.setView(view);
return mv;
}
}

写好之后,在springContext.xml配置文件中配置一下

<bean class="com.butioy.common.handler.SpringHandlerExceptionResolver"/>
然后启动tomcat,一切正常没有错误信息,但是在请求的时候并没有按照我的猜想返回自定义的错误页,而是tomcat默认的错误页

于是我就debugger跟踪一下代码执行情况。终于让我给发现了在Spring的DispatcherServlet类的processHandlerException方法中有一个handlerExceptionResolvers集合,这里面存放着声明的异常处理bean。

这时会发现这里面有3个异常处理bean是我们没有声明的,查了资料才知道原来这3个bean是SpringMVC默认初始化的,在spring-webmvc的jar包中,跟DispatcherServlet.java同一包下的DispatcherServlet.properties配置文件,配置文件内容如下:

虽然有3个默认的bean, 但是为什么优先级就高于我自定义的呢?于是我点开这些类看了一下。发现这些了都间接实现了Spring的Ordered接口。这个接口只有一个方法getOrder(),这个方法返回一个int类型值,该值就是表明这个bean的执行的优先级。上面的统一异常处理实现类的注释中也有提过,是拷贝了DefaultHandlerExceptionResolver#doResolveException方法,这里就知道上面的错误信息已经被这个类处理类。在上面debugger跟踪时, 我们知道DefaultHandlerExceptionResolver实例的bean的执行顺序是优先于我们自定义的SpringHandlerExceptionResolver的bean,所以那些404,415等错误信息一致会被SpringMVC默认的异常处理bean处理。这跟我们的初衷不符。于是我把SpringHandlerExceptionResolver改造了一下,也实现Ordered接口:

这里我默认给它一个最高优先级,这样就可以让自定义的类优先执行了。果然,结果跟我预期一样。

这里是返回错误页面:

这里是json返回数据(我这里使用的是jsonp请求方式):

至此,整个SpringMVC全局异常处理就完成了。这里我只是把ajax请求都当成请求json数据,所以就这样做了。实际依情况而定。

注意: 使用这个方法的时候切记不要在要在spring-mvc.xml中配置 <mvc:default-servlet-handler/>,应为这会使得配置的SpringHandlerExceptionResolver对404错误不起作用。在DispatcherServlet类中的doDispatch方法中我们会发现一行代码

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

这里的getHandler()会获取一个handler,如果没有与url对应handler的就会获取到
<mvc:default-servlet-handler/>声明的一个
org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler。
这样就不会执行noHandlerFound方法,也就不会抛出404异常了。
还有一点需要注意,就是在web.xml配置文件中,在配置DispatcherServlet的时候要加上

<init-param>
<!-- 如果未发现映射路径,抛出异常,而不是跳转到在web.xml配置的404错误页 -->
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>

因为如果不加上这个配置,在发生404的时候,就不会抛出异常,而是返回一个404状态的响应了 
下面是DispatcherServlet.java的源码一部分:

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}

在这里可以看到,如果throwExceptionIfNoHandlerFound为false,不会抛出异常,而是会给浏览器一个404状态的响应。而DispatcherServlet.java中这个属性的默认值就是false。

原文链接:https://blog.csdn.net/butioy_org/article/details/78718405

SpringMVC全局异常统一处理的更多相关文章

  1. Springboot项目全局异常统一处理

    转自https://blog.csdn.net/hao_kkkkk/article/details/80538955 最近在做项目时需要对异常进行全局统一处理,主要是一些分类入库以及记录日志等,因为项 ...

  2. springmvc中拦截器与springmvc全局异常处理器的问题

    最近在做一个练手的小项目, 系统架构中用了springmvc的全局异常处理器, 做了系统的统一异常处理. 后来加入了springmvc的拦截器, 为了一些需求, 在拦截器中的 preHandle 方法 ...

  3. springmvc的异常统一处理

    在项目实际开发中,异常的统一处理是一个常态.假如不使用异常统一处理,我们往往需要在service层中捕获异常,并且根据不同的异常在result中的设置不同的code并给予相应的提示.这样可能会导致不同 ...

  4. springmvc全局异常后返回JSON异常数据

    转自:http://www.cnblogs.com/exmyth/p/5601288.html (1)自定义或者使用spring自带的各种异常处理器 例如spring基于注解的异常解析器Annotat ...

  5. springmvc 全局异常解决方案

    系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减少运行时异常的发生. 系统的dao.service. ...

  6. 设置springmvc全局异常

    设置全局异常,将异常信息指定内容展示给前端页面,保证程序的安全性 @Slf4j@Componentpublic class ExceptionResolver implements HandlerEx ...

  7. Android app 全局异常统一处理

    异常处理需求 Android app 出现 crash 时,会出现 "程序异常退出" 的提示并关闭,体验不好,另外主要是无法知道哪里出现的崩溃,需要知道哪里造成的异常,就需要一个全 ...

  8. SpringMvc 全局异常处理器定义,友好的返回后端错误信息

    import com.google.common.collect.Maps; import org.apache.log4j.Logger; import org.springframework.be ...

  9. springmvc请求参数异常统一处理,结合钉钉报告信息定位bug位置

    参考之前一篇博客:springmvc请求参数异常统一处理 1.ExceptionHandlerController package com.oy.controller; import java.tex ...

随机推荐

  1. [转帖]Grafana背后的Nginx和Apache Proxy

    Grafana背后的Nginx和Apache Proxy https://ywnz.com/linuxyffq/5590.html 这个网站貌似非常非常好 在本文中,我将向你展示如何在Nginx和Ap ...

  2. GridControl gridView显示筛选行,设置条件为包含

    public static void SetFilter(GridView gdv) {     gdv.OptionsView.ShowAutoFilterRow = true; //设置筛选行  ...

  3. Spring IOC的底层实现原理

     PS:模块之间的相互依赖叫做耦合 传统方式的开发 UserService us=new UserService(); || v 面向接口编程 UserService us=new UserServi ...

  4. laravel-admin关联查询问题解决办法

    文档是这么说的: 按照文档上来,没有成功,网上找了好久,说是没有在模型中关联,关联之后的运行结果是这样的: 还是没有成功啊,仔细研究返现是这里写错了,whereHas后面跟的是model中的方法名,而 ...

  5. CSS基础布局

    目录 css基础布局 1.布局相关的标签 2.盒子模型 2-1 什么是盒子模型 2-2 块级元素和内联元素(行内元素) 2-3 盒子模型之间的关系 盒子模型相关CSS属性 3.浮动 3-1 什么是浮动 ...

  6. 怎样查看或修改html的绝对路径

    查看用 Node.prototype.baseURI, 修改用 <base>; document.baseURI; // https://www.cnblogs.com/aisowe // ...

  7. python 小数精度控制

    可以用:round(数值,保留小数位数) 详情参考 https://www.cnblogs.com/herbert/p/3402245.html

  8. win10上使用php与python实现与arduino串口通信

    注意: php 需要php7,安装及开启php_dio.dll com口按照实际的进行设置,如果不知道可以打开arduino编辑器进行查看 可以与用户实现命令行交互,但是效率过慢,不清楚如何优化,使用 ...

  9. ReadWriteLock读写之间互斥吗

    开发中遇到并发的问题一般会用到锁,Synchronized存在明显的一个性能问题就是读与读之间互斥:ReadWriteLock是JDK5中提供的读写分离锁.读写分离锁可以有效地帮助减少锁竞争,以提升系 ...

  10. java后台读取配置文件

    前几天开发时遇到一个问题,在后台读取配置文件的时候无法读取属性值,于是上网查了查,现在在这分享给大家: 先附上代码吧: package com.shafei.util; import java.io. ...