Spring MVC 全局异常处理-RESTAPI接口返回统一J
写之前大概两周草草的将一些代码保存在草稿箱,今天有空来看,结果都没有了【怨念】---重新整理一下了 -----【转载请标注出处】
- 第一部分:需求
- 第二部分:实现方式
- 第三部分: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、程序员赵鑫、
最终总结
联系点这里