springboot全局异常处理_SpringMVC全局异常处理
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全局异常处理相关推荐
- RestControllerAdvice作用及原理---自定义异常处理(全局异常处理)
前言 一直想开发一个功能比较强大的项目,但是一直没有动手,最近终于有点时间来折腾它了.由于时隔两年没有接触前端了,所以需要一个小项目先练练手感.等这个项目完工之后在着手搞一个大工程.都说好记星不如烂笔 ...
- springboot springmvc 抛出全局异常解决方法
springboot springmvc 抛出全局异常解决方法 参考文章: (1)springboot springmvc 抛出全局异常解决方法 (2)https://www.cnblogs.com/ ...
- springBoot(5)---单元测试,全局异常
springBoot(5)---单元测试,全局异常 参考文章: (1)springBoot(5)---单元测试,全局异常 (2)https://www.cnblogs.com/qdhxhz/p/903 ...
- SpringBoot RESTful 应用中的异常处理小结
SpringBoot RESTful 应用中的异常处理小结 参考文章: (1)SpringBoot RESTful 应用中的异常处理小结 (2)https://www.cnblogs.com/Zomb ...
- springboot 2.0 配置全局时间格式化
springboot 2.0 配置全局时间格式化 方式一: 在yml配置文件中添加以下配置 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zo ...
- SpringBoot项目中的全局异常处理器 Failed to invoke @ExceptionHandler method
SpringBoot项目中的全局异常处理器 Failed to invoke @ExceptionHandler method 参考文章: (1)SpringBoot项目中的全局异常处理器 Faile ...
- ssm 异常捕获 统一处理_SpringMVC 统一异常处理介绍及实战
背景 什么是统一异常处理 目标 统一异常处理实战 用 Assert(断言) 替换 throw exception 定义统一异常处理器类 扩展 总结 <Java 2019 超神之路> < ...
- R语言attach函数、detach函数(全局注册或者全局解除)实战
R语言attach函数.detach函数(全局注册或者全局解除)实战 目录 R语言attach函数.detach函数(全局注册或者全局解除)实战 #基本语法 # 仿真数据 # 如果没有attach就直 ...
- python中什么是异常,python中异常处理,python异常处理,什么是异常?异常是一
python中异常处理,python异常处理,什么是异常?异常是一 什么是异常? 异常是一个事件,该事件会在程序的执行中发生,影响程序的正常运行,一般情况下,在python无法正常处理程序时,就会发生 ...
- mysql 分区 全局索引_全局分区索引与局部分区索引
分区索引 分区索引,有是全局分区索引与局部分区索引,加上一种全局非分区索引(也就是普通索引),加起来共三种.下面我们讨论了这三种索引的组织结构以及应用场景. 1.全局非分区索引可以依赖普通的表,也可以 ...
最新文章
- 计算机的桌面图标类型,给“我的电脑”翻身 另类桌面图标排列
- premiere pr 某个面板悬浮后怎么还原
- 基于android的记账本论文,(毕业论文)基于安卓的记账本.doc
- suse11.3下samba服务的配置
- python tkinter火柴人_趣学Python编程
- 启动Eclipse 弹出“Failed to load the JNI shared library”错误的解决方法
- React Native开发总结(一)
- [Java反射基础四]通过反射了解集合泛型的本质
- 好程序员大数据视频教程之快速入门Scala篇
- Kali Linux全网最细安装教程
- 【物联网+GIS】让传感器数据在三维地图上显示,更直观,更震撼!
- 【Cocos 3d】粒子特效的制作与使用
- Elastic 7.13.0 版重磅发布:在 Elastic 上搜索和存储更多数据
- 苹果电脑安装软件显示:映像数据已损坏的解决办法
- 我的Windows工具之文件查重工具——DuplicateCleaner
- 抑郁研究所融资历程分享--以太一堂--直播课
- 电脑计算机显示调用失败和未执行,远程调用过程失败且未执行的详细处理方法...
- jxl 统计图_cad的图形为什么会自动重叠成两层图形
- css中元素横向放置,使用CSS将元素放置到右侧
- 记软测面试问题(1)