springboot全局异常统一拦截

前言:

  spring boot学习以及使用也有一段时间了,平时疏于整理资料,故今日写一篇关于全局异常的资料

背景:

  异常处理是为了给用户带来良好的交互体验

异常:

  1. 访问了错误的页面,或者是非法的访问导致服务器不能返回正常的数据,例如访问了不存在的页面导致404
  2. 程序代码内部的错误,在开发时期由于没有考虑周全导致的程序异常,列如常见的空指针异常(NullPointException

操作:

关于第一种处理:


import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
import javax.servlet.http.HttpServletRequest;
 
 
@Controller
class MainsiteErrorController implements ErrorController {
     
 
    @RequestMapping("/error")
    public String handleError(HttpServletRequest request) {
     
        System.out.println("成功拦截异常信息");
        //获取statusCode:401,404,500
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == 401) {
     
            return "/error.html";
        } else if (statusCode == 404) {
     
            return "/404.html";
        } else if (statusCode == 403) {
     
            return "/error.html";
        } else if (statusCode == 500) {
     
            return "/error.html";
        } else {
     
            return "/error.html";
        }
    }
 
    @Override
    public String getErrorPath() {
     
        return "/error";
    }
 
}

关于第二种处理:
SpringBoot中自带的异常捕获机制返回的默认页面比较丑,对用户来说不够人性化。所以这篇文章来讲解SpringBoot钟自定义全局异常捕获。
主要讲解三种异常捕获形式:

页面跳转异常
ajax异常
统一返回异常的形式
分别来看三种形式的全局异常捕获:

1.页面跳转异常

首先新建一个异常:

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";
    }
}

浏览器请求,会得到SpringBoot默认的异常页面:
springboot全局异常统一拦截_第1张图片


新建异常处理工具类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:

<!DOCTYPE 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:

得到了我们想要的结果,小伙们可以打断点看一下。当然这个只是一个测试页面,并不美观。正式环境中当然会有专业的美工和前端来设计报错页面。

2.ajax异常

如果出现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:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.springframework.org/schema/mvc">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <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捕获到,然后将异常信息等返回前台。


3.统一异常处理

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调试可以更清楚的看到程序的执行过程。

你可能感兴趣的:(SpringBoot)