SpringMVC全局异常处理

SpringMVC除了可以做URL映射请求拦截外,还可以做全局异常的处理。全局异常处理可能我们平时比较少机会接触,但是每个项目都肯定会做这个处理。比如在上一间公司,是前后端分离的架构,所以后端只要有运行时异常就会报“系统异常,请稍后再试”。如果想要走上架构师的话,这个肯定是要学会的。

SpringMVC全局异常处理机制

首先,要知道全局异常处理,SpringMVC提供了两种方式:

  • 实现HandlerExceptionResolver接口,自定义异常处理器。
  • 使用HandlerExceptionResolver接口的子类,也就是SpringMVC提供的异常处理器。

所以,总得来说就两种方式,一种是自定义异常处理器,第二种是SpringMVC提供的。接下来先说SpringMVC提供的几种异常处理器的使用方式,然后再讲自定义异常处理器。

SpringMVC提供的异常处理器有哪些呢?我们可以直接看源码的类图。

可以看出有四种:

  • DefaultHandlerExceptionResolver,默认的异常处理器。根据各个不同类型的异常,返回不同的异常视图。
  • SimpleMappingExceptionResolver,简单映射异常处理器。通过配置异常类和view的关系来解析异常。
  • ResponseStatusExceptionResolver,状态码异常处理器。解析带有@ResponseStatus注释类型的异常。
  • ExceptionHandlerExceptionResolver,注解形式的异常处理器。对@ExceptionHandler注解的方法进行异常解析。

DefaultHandlerExceptionResolver

这个异常处理器是SprngMVC默认的一个处理器,处理一些常见的异常,比如:没有找到请求参数,参数类型转换异常,请求方式不支持等等。

接着我们看DefaultHandlerExceptionResolver类的doResolveException()方法:

    @Override@Nullableprotected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) {try {if (ex instanceof HttpRequestMethodNotSupportedException) {return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,response, handler);}else if (ex instanceof HttpMediaTypeNotSupportedException) {return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,handler);}else if (ex instanceof HttpMediaTypeNotAcceptableException) {return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,handler);}//省略...以下还有十几种异常的else-if}catch (Exception handlerException) {//是否打开日志,如果打开,那就记录日志if (logger.isWarnEnabled()) {logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);}}return null;}

通过if-else判断,判断继承什么异常就显示对应的错误码和错误提示信息。由此可以知道,处理一般有两步,一是设置响应码,二是在响应头设置异常信息。下面是MissingServletRequestPartException的处理的源码:

    protected ModelAndView handleMissingServletRequestPartException(MissingServletRequestPartException ex,HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {//设置响应码,设置异常信息,SC_BAD_REQUEST就是400(bad request)response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());return new ModelAndView();}//响应码public static final int SC_BAD_REQUEST = 400;

为什么要存在这个异常处理器呢?

从框架的设计理念来看,这种公共的、常见的异常应该交给框架本身来完成,是一些必需处理的异常。比如参数类型转换异常,如果程序员不处理,还有框架提供默认的处理方式,不至于出现这种错误而无法排查

SimpleMappingExceptionResolver

这种异常处理器需要提前配置异常类和对应的view视图。一般用于使用JSP的项目中,出现异常则通过这个异常处理器跳转到指定的页面。

怎么配置?首先搭建JSP项目我就不浪费篇幅介绍了。首先要加载一个XML文件。

@SpringBootApplication
//在启动类,加载配置文件
@ImportResource("classpath:spring-config.xml")
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

然后在resources目录下,创建一个spring-config.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><!-- 定义默认的异常处理页面 --><property name="defaultErrorView" value="err"/><!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception --><property name="exceptionAttribute" value="ex"/><!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常也页名作为值 --><property name="exceptionMappings"><props><!-- 数组越界异常 --><prop key="java.lang.ArrayIndexOutOfBoundsException">err/arrayIndexOutOfBounds</prop><!-- 空指针异常 --><prop key="java.lang.NullPointerException">err/nullPointer</prop></props></property></bean>
</beans>

然后在webapp也就是存放JSP页面的目录下,创建两个JSP页面。

arrayIndexOutOfBounds.jsp如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>数组越界异常</title>
</head>
<body>
<h1>数组越界异常</h1>
<br>
<%-- 打印异常到页面上 --%>
<% Exception ex = (Exception)request.getAttribute("ex"); %>
<br>
<div><%= ex.getMessage() %></div>
<% ex.printStackTrace(new java.io.PrintWriter(out)); %>
</body>
</html>

nullPointer.jsp如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>空指针异常</title>
</head>
<body>
<h1>空指针异常</h1>
<br>
<%-- 打印异常到页面上 --%>
<% Exception ex = (Exception)request.getAttribute("ex"); %>
<br>
<div><%=ex.getMessage()%></div>
<% ex.printStackTrace(new java.io.PrintWriter(out)); %>
</body>
</html>

接着创建两个Controller,分别抛出空指针异常和数组越界异常。

@Controller
@RequestMapping("/error")
public class ErrController {@RequestMapping("/null")public String err() throws Exception{String str = null;//抛出空指针异常int length = str.length();System.out.println(length);return "index";}@RequestMapping("/indexOut")public String indexOut() throws Exception{int[] nums = new int[2];for (int i = 0; i < 3; i++) {//抛出数组越界异常nums[i] = i;System.out.println(nums[i]);}return "index";}
}

启动项目后,我们发送两个请求,就可以看到:

通过上述例子可以看出,其实对于现在前后端分离的项目来说,这种异常处理器已经不是很常用了

ResponseStatusExceptionResolver

这种异常处理器主要用于处理带有@ResponseStatus注释的异常。下面演示一下使用方式。

首先自定义异常类继承Exception,并且使用@ResponseStatus注解修饰。如下:

//value需要使用HttpStatus枚举类型,HttpStatus.FORBIDDEN=403。
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "My defined Exception")
public class DefinedException extends Exception{}

然后再在Controller层抛出此异常。如下:

@Controller
@RequestMapping("/error")
public class ErrController {@RequestMapping("/myException")public String ex(@RequestParam(name = "num") Integer num) throws Exception {if (num == 1) {//抛出自定义异常throw new DefinedException();}return "index";}
}

然后启动项目,请求接口,可以看到如下信息:

使用这种异常处理器,需要自定义一个异常,一定要一直往上层抛出异常,如果不往上层抛出,在service或者dao层就try-catch处理掉的话,是不会触发的。

ExceptionHandlerExceptionResolver

这个异常处理器才是最重要的,也是最常用,最灵活的,因为是使用注解。首先我们还是简单地演示一下怎么使用:

首先需要定义一个全局的异常处理器。

//这里使用了RestControllerAdvice,是@ResponseBody和@ControllerAdvice的结合
//会把实体类转成JSON格式的提示返回,符合前后端分离的架构
@RestControllerAdvice
public class GlobalExceptionHandler {//这里自定义了一个BaseException,当抛出BaseException异常就会被此方法处理@ExceptionHandler(BaseException.class)public ErrorInfo errorHandler(HttpServletRequest req, BaseException e) throws Exception {ErrorInfo r = new ErrorInfo();r.setMessage(e.getMessage());r.setCode(ErrorInfo.ERROR);r.setUrl(req.getRequestURL().toString());return r;}
}

然后我们自定义一个自定义异常类BaseException

public class BaseException extends Exception {public BaseException(String message) {super(message);}
}

然后在Controller层定义一个方法测试:

@Controller
@RequestMapping("/error")
public class ErrController {@RequestMapping("/base")public String base() throws BaseException {throw new BaseException("系统异常,请稍后重试。");}
}

老规矩,启动项目,请求接口可以看到结果:

你也可以不自定义异常BaseException,而直接拦截常见的各种异常都可以。所以这是一个非常灵活的异常处理器。你也可以做跳转页面,返回ModelAndView即可(以免篇幅过长就不演示了,哈哈)。

小结

经过以上的演示后我们学习了SpringMVC四种异常处理器的工作机制,最后这种作为程序员我觉得是必须掌握的,前面的简单映射异常处理器和状态映射处理器可以选择性掌握,默认的异常处理器了解即可。

那这么多异常处理器,究竟是如何工作的呢?为什么是设计一个接口,下面有一个抽象类加上四个实现子类呢?接下来我们通过源码分析来揭开谜底!

源码分析

源码分析从哪里入手呢?在SpringMVC中,其实你想都不用想,肯定在DispatcherServlet类里。经过我顺藤摸瓜,我定位在了processHandlerException()方法。怎么定位的呢?其实很简单,看源码:

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;//异常不为空if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);//关键点:执行异常处理mv = processHandlerException(request, response, handler, exception);//省略...}}//省略...}

processHandlerException()

就是这个直接的一个if-else判断,那个processHandlerException()方法又是怎么处理的呢?

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {ModelAndView exMv = null;//判断异常处理器的集合是否为空if (this.handlerExceptionResolvers != null) {//不为空则遍历异常处理器 for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {//调用异常处理器的resolveException()方法进行处理异常exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);//判断返回的ModelAndView是否为null,不为null则跳出循环,为null则继续下一个异常处理器if (exMv != null) {break;}}}//如果ModelAndView不为空if (exMv != null) {if (exMv.isEmpty()) {//设置异常信息提示request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}//如果返回的ModelAndView不包含viewif (!exMv.hasView()) {//设置一个默认的视图 String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}//省略...//返回异常的ModelAndView    return exMv;}throw ex;
}

这不就是责任链模式吗!提前加载异常处理器到handlerExceptionResolvers集合中,然后遍历去执行,能处理就处理,不能处理就跳到下一个异常处理器处理。

那接下来我们就有一个问题了,handlerExceptionResolvers集合是怎么加载异常处理器的?这个问题很简单,就是使用DispatcherServlet.properties配置文件。这个文件真的很重要!!!

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

默认是加载以上三种异常处理器到集合中,所以只要带有@ControllerAdvice@ExceptionHandler@ResponseStatus注解的都会被扫描。SimpleMappingExceptionResolver则是通过xml文件(当然也可以使用@Configuration)去配置。

resolveException()

其实在resolveException()处理异常的方法中,还使用了模板模式。

    @Override@Nullablepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) {//省略...//预处理prepareResponse(ex, response);//调用了一个抽象方法,抽象方法由子类去实现ModelAndView result = doResolveException(request, response, handler, ex);//省略...}

抽象方法doResolveException(),由子类实现。

@Nullable
protected abstract ModelAndView doResolveException(HttpServletRequest request,HttpServletResponse response, @Nullable Object handler, Exception ex);

怎么识别模板方法,其实很简单,只要看到抽象类,有个具体方法里面调用了抽象方法,那很大可能就是模板模式。抽象方法就是模板方法,由子类实现。

子类我们都知道就是那四个异常处理器实现类了。

总结

用流程图概括一下:

经过以上的学习后,我们知道只需要把异常处理器加到集合中,就可以执行。所以我们可以使用直接实现HandlerExceptionResolver接口的方式来实现异常处理器。

实现HandlerExceptionResolver接口实现全局异常处理

首先自定一个异常类MyException

public class MyException extends Exception {public MyException(String message) {super(message);}
}

然后实现HandlerExceptionResolver接口定义一个异常处理器。

//注册异常处理器到Spring容器中
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {try {//如果属于MyException异常,则输出异常提示到页面if (ex instanceof MyException) {response.setContentType("text/html;charset=utf-8");response.getWriter().println(ex.getMessage());//这里返回null,不做处理。也可以返回ModelAndView跳转页面return null;}} catch (IOException e) {e.printStackTrace();}return null;}
}

然后在Controller层定义一个方法测试:

@Controller
@RequestMapping("/error")
public class ErrController {@RequestMapping("/myEx")public String myEx() throws MyException {System.out.println("执行myEx()");throw new MyException("自定义异常提示信息");}
}

启动项目,请求接口,我们可以看到:

最后说几句

以上就是我对于SpringMVC全局异常处理机制的理解。更多的java技术分享,可以关注我的公众号“java技术爱好者”,后续会不断更新。

http://weixin.qq.com/r/TS8ZAfTE26mkrbBi93pf (二维码自动识别)

springboot全局异常处理_SpringMVC全局异常处理相关推荐

  1. RestControllerAdvice作用及原理---自定义异常处理(全局异常处理)

    前言 一直想开发一个功能比较强大的项目,但是一直没有动手,最近终于有点时间来折腾它了.由于时隔两年没有接触前端了,所以需要一个小项目先练练手感.等这个项目完工之后在着手搞一个大工程.都说好记星不如烂笔 ...

  2. springboot springmvc 抛出全局异常解决方法

    springboot springmvc 抛出全局异常解决方法 参考文章: (1)springboot springmvc 抛出全局异常解决方法 (2)https://www.cnblogs.com/ ...

  3. springBoot(5)---单元测试,全局异常

    springBoot(5)---单元测试,全局异常 参考文章: (1)springBoot(5)---单元测试,全局异常 (2)https://www.cnblogs.com/qdhxhz/p/903 ...

  4. SpringBoot RESTful 应用中的异常处理小结

    SpringBoot RESTful 应用中的异常处理小结 参考文章: (1)SpringBoot RESTful 应用中的异常处理小结 (2)https://www.cnblogs.com/Zomb ...

  5. springboot 2.0 配置全局时间格式化

    springboot 2.0 配置全局时间格式化 方式一: 在yml配置文件中添加以下配置 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zo ...

  6. SpringBoot项目中的全局异常处理器 Failed to invoke @ExceptionHandler method

    SpringBoot项目中的全局异常处理器 Failed to invoke @ExceptionHandler method 参考文章: (1)SpringBoot项目中的全局异常处理器 Faile ...

  7. ssm 异常捕获 统一处理_SpringMVC 统一异常处理介绍及实战

    背景 什么是统一异常处理 目标 统一异常处理实战 用 Assert(断言) 替换 throw exception 定义统一异常处理器类 扩展 总结 <Java 2019 超神之路> < ...

  8. R语言attach函数、detach函数(全局注册或者全局解除)实战

    R语言attach函数.detach函数(全局注册或者全局解除)实战 目录 R语言attach函数.detach函数(全局注册或者全局解除)实战 #基本语法 # 仿真数据 # 如果没有attach就直 ...

  9. python中什么是异常,python中异常处理,python异常处理,什么是异常?异常是一

    python中异常处理,python异常处理,什么是异常?异常是一 什么是异常? 异常是一个事件,该事件会在程序的执行中发生,影响程序的正常运行,一般情况下,在python无法正常处理程序时,就会发生 ...

  10. mysql 分区 全局索引_全局分区索引与局部分区索引

    分区索引 分区索引,有是全局分区索引与局部分区索引,加上一种全局非分区索引(也就是普通索引),加起来共三种.下面我们讨论了这三种索引的组织结构以及应用场景. 1.全局非分区索引可以依赖普通的表,也可以 ...

最新文章

  1. 计算机的桌面图标类型,给“我的电脑”翻身 另类桌面图标排列
  2. premiere pr 某个面板悬浮后怎么还原
  3. 基于android的记账本论文,(毕业论文)基于安卓的记账本.doc
  4. suse11.3下samba服务的配置
  5. python tkinter火柴人_趣学Python编程
  6. 启动Eclipse 弹出“Failed to load the JNI shared library”错误的解决方法
  7. React Native开发总结(一)
  8. [Java反射基础四]通过反射了解集合泛型的本质
  9. 好程序员大数据视频教程之快速入门Scala篇
  10. Kali Linux全网最细安装教程
  11. 【物联网+GIS】让传感器数据在三维地图上显示,更直观,更震撼!
  12. 【Cocos 3d】粒子特效的制作与使用
  13. Elastic 7.13.0 版重磅发布:在 Elastic 上搜索和存储更多数据
  14. 苹果电脑安装软件显示:映像数据已损坏的解决办法
  15. 我的Windows工具之文件查重工具——DuplicateCleaner
  16. 抑郁研究所融资历程分享--以太一堂--直播课
  17. 电脑计算机显示调用失败和未执行,远程调用过程失败且未执行的详细处理方法...
  18. jxl 统计图_cad的图形为什么会自动重叠成两层图形
  19. css中元素横向放置,使用CSS将元素放置到右侧
  20. 记软测面试问题(1)

热门文章

  1. 小菜鸟一步步打造图书馆外挂之十六:手动启动入口的实现
  2. 聊聊索引失效的10种场景,巨坑
  3. @Value竟然能玩出这么多花样,涨知识了
  4. Nginx+Tomcat负载平衡
  5. 王彪20162321 2016-2017-2 《程序设计与数据结构》第4周学习总结
  6. Poj(1274),二分图匹配
  7. Android入门(三)Activity-生命周期与启动模式
  8. 数据结构实践——用哈希法组织关键字
  9. ZeptoLab Code Rush 2015 B. Om Nom and Dark Park DFS
  10. 【uTenux实验】中断处理