SpringMVC 异常处理

基本概念

在 SpringMVC 中 HandlerExceptionResolver 接口负责统一异常处理。

内部构造

下面来看它的源码:

1public interface HandlerExceptionResolver { 2 3 ModelAndView resolveException( 4 HttpServletRequest request, HttpServletResponse response, 5 Object handler, Exception ex); 6}

AbstractHandlerMethodExceptionResolver

该类是实现了 HandlerExceptionResolver 接口的抽象实现类。关键来看 resolveException 方法:

1public ModelAndView resolveException(HttpServletRequest request, 2 HttpServletResponse response, Object handler, Exception ex) { 3 4 // 1.判断是否支持该处理器 5 if (shouldApplyTo(request, handler)) { 6 7 // 省略部分源码... 8 9 // 2.预处理响应消息,让请求头取消缓存 10 prepareResponse(ex, response); 11 12 // 3.异常处理,空方法 13 ModelAndView mav = doResolveException(request, response, handler, ex); 14 if (mav != null) { 15 logException(ex, request); 16 } 17 18 return mav; 19 20 } else { 21 return null; 22 } 23}

接着来看 shouldApplyTo 方法:

1protected boolean shouldApplyTo(HttpServletRequest request, Object handler) { 2 if (handler != null) { 3 // 分别比对 mappedHandlers 、mappedHandlerClasses 4 if (this.mappedHandlers != null && 5 this.mappedHandlers.contains(handler)) { 6 return true; 7 } 8 9 if (this.mappedHandlerClasses != null) { 10 for (Class<?> handlerClass : this.mappedHandlerClasses) { 11 if (handlerClass.isInstance(handler)) { 12 return true; 13 } 14 } 15 } 16 } 17 18 return (this.mappedHandlers == null && 19 this.mappedHandlerClasses == null); 20}

SimpleMappingExceptionResolver

它继承了 AbstractHandlerMethodExceptionResolver 。实现了真正的异常处理。

来看该类的 doResolveException 方法:

1protected ModelAndView doResolveException(HttpServletRequest request, 2 HttpServletResponse response, Object handler, Exception ex) { 3 4 // 1.决定的视图 5 String viewName = determineViewName(ex, request); 6 7 if (viewName != null) { 8 // 2.决定错误状态码 9 Integer statusCode = determineStatusCode(request, viewName); 10 if (statusCode != null) { 11 3.设置错误状态码 12 applyStatusCodeIfPossible(request, response, statusCode); 13 } 14 15 // 4.返回错误页面 16 return getModelAndView(viewName, ex, request); 17 } else { 18 return null; 19 } 20}

1.决定视图

1// 被过滤的异常集合 2private Class<?>[] excludedExceptions; 3 4// 被处理的异常集合 5private Properties exceptionMappings; 6 7// 默认的错误显示页面 8private String defaultErrorView; 9 10protected String determineViewName(Exception ex, HttpServletRequest request) { 11 12 String viewName = null; 13 14 // 1.判断属于被过滤的异常? 15 if (this.excludedExceptions != null) { 16 for (Class<?> excludedEx : this.excludedExceptions) { 17 if (excludedEx.equals(ex.getClass())) { 18 return null; 19 } 20 } 21 } 22 23 // 2.判断属于被处理的异常? 24 if (this.exceptionMappings != null) { 25 // 找到匹配的页面 26 viewName = findMatchingViewName(this.exceptionMappings, ex); 27 } 28 29 // 3.为空则使用默认的错误页面 30 if (viewName == null && this.defaultErrorView != null) { 31 viewName = this.defaultErrorView; 32 } 33 return viewName; 34}

接着来看 findMatchingViewName 方法:

1protected String findMatchingViewName(Properties exceptionMappings, Exception ex) { 2 String viewName = null; 3 String dominantMapping = null; 4 int deepest = Integer.MAX_VALUE; 5 6 // 遍历 exceptionMappings 7 for (Enumeration<?> names = exceptionMappings.propertyNames(); 8 names.hasMoreElements();) { 9 10 String exceptionMapping = (String) names.nextElement(); 11 12 // 关键 -> 匹配异常 13 int depth = getDepth(exceptionMapping, ex); 14 15 if (depth >= 0 && 16 ( depth < deepest || 17 (depth == deepest && 18 dominantMapping != null && 19 exceptionMapping.length() > dominantMapping.length() ) )) { 20 21 deepest = depth; 22 dominantMapping = exceptionMapping; 23 24 viewName = exceptionMappings.getProperty(exceptionMapping); 25 } 26 } 27 28 // 省略代码... 29 30 return viewName; 31}

继续来看 getDepth 方法:

1protected int getDepth(String exceptionMapping, Exception ex) { 2 // 匹配返回 0,不匹配返回 -1 ,depth 越低的好 3 return getDepth(exceptionMapping, ex.getClass(), 0); 4} 5 6private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) { 7 if (exceptionClass.getName().contains(exceptionMapping)) { 8 return depth; 9 } 10 11 if (exceptionClass == Throwable.class) { 12 return -1; 13 } 14 15 return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1); 16}

2.决定错误状态码

1// 在配置文件中定义 2private Map<String, Integer> statusCodes = new HashMap<String, Integer>(); 3 4protected Integer determineStatusCode(HttpServletRequest request, String viewName) { 5 if (this.statusCodes.containsKey(viewName)) { 6 return this.statusCodes.get(viewName); 7 } 8 return this.defaultStatusCode; 9}

3.设置错误状态码

1public static final String ERROR_STATUS_CODE_ATTRIBUTE = 2 "javax.servlet.error.status_code"; 3 4protected void applyStatusCodeIfPossible(HttpServletRequest request, 5 HttpServletResponse response, int statusCode) { 6 7 if (!WebUtils.isIncludeRequest(request)) { 8 // 省略代码... 9 10 // 设置错误状态码,并添加到 request 的属性 11 response.setStatus(statusCode); 12 request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode); 13 } 14}

4.返回错误页面

1protected ModelAndView getModelAndView(String viewName, Exception ex, 2 HttpServletRequest request) { 3 return getModelAndView(viewName, ex); 4} 5 6protected ModelAndView getModelAndView(String viewName, Exception ex) { 7 ModelAndView mv = new ModelAndView(viewName); 8 if (this.exceptionAttribute != null) { 9 // 省略代码... 10 11 mv.addObject(this.exceptionAttribute, ex); 12 } 13 return mv; 14}

实例探究

下面来看 springmvc 中常见的统一异常处理方法。

1.实现 HandlerExceptionResolver 接口

首先需要实现 HandlerExceptionResolver 接口。

1public class MyExceptionResolver implements HandlerExceptionResolver { 2 @Override 3 public ModelAndView resolveException(HttpServletRequest request, 4 HttpServletResponse response, Object handler, Exception ex) { 5 6 // 异常处理... 7 8 // 视图显示专门的错误页 9 ModelAndView modelAndView = new ModelAndView("error"); 10 return modelAndView; 11 } 12}

配置到 spring 配置文件中,或者加上@Component 注解。

<bean  class="com.resolver.MyExceptionResolver"/>

2.添加 @ExceptionHandler 注解

首先来看它的注解定义:

1// 只能作用在方法上,运行时有效 2Target(ElementType.METHOD) 3Retention(RetentionPolicy.RUNTIME) 4@Documented 5public @interface ExceptionHandler { 6 7 // 这里可以定义异常类型,为空表示匹配任何异常 8 Class<? extends Throwable>[] value() default {}; 9}

在控制器中使用,可以定义不同方法来处理不同类型的异常。

1public abstract class BaseController { 2 // 处理 IO 异常 3 @ExceptionHandler(IOException.class) 4 public ModelAndView handleIOException(HttpServletRequest request, HttpServletResponse response, Exception e) { 5 6 // 视图显示专门的错误页 7 ModelAndView modelAndView = new ModelAndView("error"); 8 9 return modelAndView; 10 } 11 12 // 处理空指针异常 13 @ExceptionHandler(NullPointerException.class) 14 public ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Exception e) { 15 16 // 视图显示专门的错误页 17 ModelAndView modelAndView = new ModelAndView("error"); 18 19 return modelAndView; 20 } 21}

使用 @ExceptionHandler 注解实现异常处理有个缺陷就是只对该注解所在的控制器有效。

想要让所有的所有的控制器都生效,就要通过继承来实现。

如上所示(定义了一个抽象的基类控制器) ,可以让其他控制器继承它实现异常处理。

public class HelloController extends BaseController 

3.利用 SimpleMappingExceptionResolver 类

该类实现了 HandlerExceptionResolver 接口,是 springmvc 默认实现的类,通过它可以实现灵活的异常处理。

只需要在 xml 文件中进行如下配置:

1<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> 2 <property name="exceptionMappings"> 3 <props> 4 <prop key="NullPointerException">nullpointpage</prop> 5 <prop key="IOException">iopage</prop> 6 <prop key="NumberFormatException">numberpage</prop> 7 </props> 8 </property> 9 <property name="statusCodes"> 10 <props> 11 <prop key="nullpointpage">400</prop> 12 <prop key="iopage">500</prop> 13 </props> 14 </property> 15 <property name="defaultErrorView" value="errorpage"/> 16 <property name="defaultStatusCode" value="404"/> 17</bean>

-exceptionMappings:定义 springmvc 要处理的异常类型和对应的错误页面;

-statusCodes:定义错误页面和response 中要返回的错误状态码

-defaultErrorView:定义默认错误显示页面,表示处理不了的异常都显示该页面。在这里表示 springmvc 处理不了的异常都跳转到 errorpage页面。

-defaultStatusCode:定义 response 默认返回的错误状态码,表示错误页面未定义对应的错误状态码时返回该值;在这里表示跳转到 errorpage、numberpage 页面的 reponse 状态码为 404。

4.ajax 异常处理

对于页面 ajax 的请求产生的异常不就适合跳转到错误页面,而是应该是将异常信息显示在请求回应的结果中。

实现方式也很简单,需要继承了 SimpleMappingExceptionResolver ,重写它的异常处理流程(下面会详细分析)。

1public class CustomSimpleMappingExceptionResolver extends SimpleMappingExceptionResolver { 2 3 @Override 4 protected ModelAndView doResolveException(HttpServletRequest request, 5 HttpServletResponse response, Object handler, Exception ex) { 6 7 // 判断是否 Ajax 请求 8 if ((request.getHeader("accept").indexOf("application/json") > -1 || 9 (request.getHeader("X-Requested-With") != null && 10 request.getHeader("X-Requested-With").indexOf("XMLHttpRequest") > -1))){ 11 12 try { 13 response.setContentType("text/html;charset=UTF-8"); 14 response.setCharacterEncoding("UTF-8"); 15 PrintWriter writer = response.getWriter(); 16 writer.write(ex.getMessage()); 17 writer.flush(); 18 writer.close(); 19 } catch (Exception e) { 20 LogHelper.info(e); 21 } 22 return null; 23 } 24 25 return super.doResolveException(request, response, handler, ex); 26 } 27}

配置文件如上,只是将注入的 Bean 替换成我们自己的定义的 CustomSimpleMappingExceptionResolver 。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )