SpringBoot中自带的异常捕获机制返回的默认页面比较丑,对用户来说不够人性化。所以这篇文章来讲解SpringBoot钟自定义全局异常捕获。
本文的源码已经上传GitHub:https://github.com/haozz1994/exceptiondemo
主要讲解三种异常捕获形式:
分别来看三种形式的全局异常捕获:
首先新建一个异常:
package com.haozz.exceptiondemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author haozz
* @date 2018/6/19 16:57
* @description
*/
@Controller
@RequestMapping(value = "/err")
public class ErrorCtrl {
@RequestMapping(value = "/error")
public String error(){
int a = 1/0;
return "abcdefg";
}
}
新建异常处理工具类MyExceptionHandler:
package com.haozz.exceptiondemo.utils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author haozz
* @date 2018/6/19 17:16
* @description
*/
@ControllerAdvice
public class MyExceptionHandler {
public static final String ERROR_VIEW = "my_error";
@ExceptionHandler(value = Exception.class)//指定拦截的异常
public Object errorHandler(HttpServletRequest request, HttpServletResponse response,Exception e) throws Exception{
e.printStackTrace();//打印异常信息
ModelAndView mv = new ModelAndView();
mv.addObject("exception",e);
mv.addObject("url",request.getRequestURL());//发生异常的路径
mv.setViewName(ERROR_VIEW);//指定发生异常之后跳转页面
return mv;
}
}
这里我们使用了@ControllerAdvice标注,可以将其理解为增强型控制器,想要了解关于其更多信息请自行百度。@ExceptionHandler标注用于指定需要拦截的异常。其余的代码没有需要过多解释的,目的就是在系统中发生了指定的异常时,进行捕获,并返回指定的异常处理路径。我们在resources/templates下新建my_error.html:
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>程序员迷路了title>
head>
<body>
<h1 style="color:red">程序员迷路了:h1>
<div th:text="${url}">div>
<div th:text="${exception.message}">div>
body>
html>
这里有一点需要强调:我之前新建的异常跳转页面取名为error.html,这样会报错EL1008L:
org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'timestamp' cannot be found on object of type 'java.util.HashMap...
后来在这篇帖子中找到了解决办法:http://makaidong.com/FlyAway2013/104144_12644765.html,我的理解是,SpringBoot中有默认的异常跳转路径(可能命名为error),而且其中有timestamp的属性,而当我们发生异常指定其跳转至error.html时,系统会以为是要跳转至默认的异常处理路径,就会去加载timestamp属性,这时当然是加载不到的,因为我们并没有设置这个属性。所以,这里最好不要将自定义的异常跳转路径命名为error。
我们再在浏览器中请求err/error:
得到了我们想要的结果,小伙们可以打断点看一下。当然这个只是一个测试页面,并不美观。正式环境中当然会有专业的美工和前端来设计报错页面。
如果出现ajax异常而不捕获的话,就会一直loading,用户体验更加不好。所以ajax请求也需要处理。
我们首先要将之前创建的MyExceptionHandler类删除掉,避免其产生影响(只需要将类头部的@ControllerAdvice标注和方法头部的@ExceptionHandler标注注释掉即可)
然后新建ajax形式的异常处理工具类,命名为MyAjaxExceptionHandler:
package com.haozz.thymeleafdemo.utils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @author haozz
* @date 2018/6/19 18:41
* @description
*/
@RestControllerAdvice
public class MyAjaxExceptionHandler {
@ExceptionHandler(value = Exception.class)
public CommonRspVo defaultErrorHandler(HttpServletRequest request,Exception e)throws Exception{
e.printStackTrace();
CommonRspVo rsp = new CommonRspVo("555",e.getMessage());
return rsp;
}
}
这里和之前页面跳转异常的不同在于类头部的标注换成了@RestControllerAdvice,其他的地方基本一致。在这里,当ajax异常发生时,我使用了一个简单的包装类返回信息给前台。
在ErrorCtrl中新增两个方法:
//跳转到发生ajax异常页面
@RequestMapping("/ajaxerror")
public String ajaxerror(){
return "thymeleaf/ajaxerror";
}
//发生ajax异常
@RequestMapping("/getAjaxerror")
public int getAjaxerror(){
int a = 1/0;
return a;
}
其中ajaxerror方法用于跳转到前台,getAjaxerror方法用于接收前台ajax请求并制造异常。我们新建ajaxerror.html及ajaxerror.js:
<html lang="en" xmlns:th="http://www.springframework.org/schema/mvc">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script type="text/javascript" th:src="@{/js/jquery.min.js}">script>
head>
<body>
<h1>测试ajax错误异常h1>
<script type="text/javascript" th:src="@{/js/ajaxerror.js}">script>
body>
html>
$.ajax({
url:"/err/getAjaxerror",
type:"POST",
async:false,
success:function(data){
debugger;
if(data.code == "200"){
alert("success!");
}else {
alert("发生异常: "+ data.message);
}
},
error:function(){
alert("error");
}
})
在浏览器中发起请求,跳转至ajaxerror.html页面,立即向后台发起ajax请求,并发生1/0异常,这时会被自定义的MyAjaxExceptionHandler捕获到,然后将异常信息等返回前台。
web页面跳转和ajax异常的统一异常处理也是非常简单的,主要就是整合一下前两步的思路。可以判断前端请求request是否是ajax请求,然后采取不同的措施。
我们还是使用第一步中使用的MyExceptionHandler异常处理助手类,将之前写好的errorHandler方法注释掉,并在其中添加如下两个方法:
@ExceptionHandler(value = Exception.class)
public Object errorHandler(HttpServletRequest request,HttpServletResponse response,Exception e)throws Exception{
e.printStackTrace();
if(isAjax(request)){//是ajax请求
CommonRspVo rsp = new CommonRspVo("555",e.getMessage());
return rsp;
}else{//不是ajax请求
ModelAndView mv = new ModelAndView();
mv.addObject("exception",e);
mv.addObject("url",request.getRequestURL());//发生异常的路径
mv.setViewName(ERROR_VIEW);//指定发生异常之后跳转页面
return mv;
}
}
private boolean isAjax(HttpServletRequest request){//判断request是否是ajax请求
return (request.getHeader("X-Requested-With") != null
&& "XMLHttpRequest"
.equals( request.getHeader("X-Requested-With").toString()) );
}
其中,isAjax方法用于判断HttpServletRequest是否是ajax请求。
这里需要注意的一点是,我们需要将MyExceptionHandler类头部的@ControllerAdvice标注修改为@RestControllerAdvice,否则在进行ajax异常捕获之后会报出TemplateInputException。
我们可以根据之前的err/error和err/ajaxerror路径分别进行页面跳转异常和ajax异常的测试,通过dubug调试可以更清楚的看到程序的执行过程。