在web项目中,不可避免地会出现系统异常,例如资源找不到的404错误,服务器异常的5**错误,如果对这些异常不进行任何处理,则是非常不友好的。今天就对springboot项目中的异常拦截处理进行一个简单的使用说明。这里我使用的是springboot默认的异常拦截,也就是新建一个BasicErrorController类继承BasicErrorController。

默认异常拦截

在springboot项目中,不管是404错误还是其他错误,浏览器访问的时候,都会抛出一个非常不友好的异常,如下

springboot全局请求拦截 springboot全局拦截header_后端

如果在客户端(非ajax)请求,则会返回一个json对象,如下

springboot全局请求拦截 springboot全局拦截header_后端_02

这样的异常是非常不友好的,接下来我们需要来对异常进行加工处理。

thymeleaf定制化页面

这里使用thymeleaf模板来新建页面。在pom中添加依赖即可。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.4.1</version>
  </dependency>

新建一个继承BaseErrorController的类

在项目下新建一个error(可任意)包,新建一个继承BaseErrorController的类。如下

springboot全局请求拦截 springboot全局拦截header_后端_03

 

springboot全局请求拦截 springboot全局拦截header_spring_04

BaseErrorController里面有对异常进行拦截,我们可以进入到该类的源码中查看

springboot全局请求拦截 springboot全局拦截header_java_05

 

springboot全局请求拦截 springboot全局拦截header_java_06

可以看到在errorHtml()中返回了异常页面,在error()中返回了postman(非ajax)请求。

那么我们自定义的类继承该类写这两个方法,就可以实现异常的自定义拦截

package meicius.ori.error;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ModelAndView;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
 
@Controller
public class MyBasicErrorController extends BasicErrorController {
   
    public MyBasicErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }
 
    @Override
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        return super.getErrorAttributes(request, includeStackTrace);
    }
 
    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        ModelAndView modelAndView = super.errorHtml(request, response);
        String httpStatus = modelAndView.getModel().get("status").toString();
        if(httpStatus.equals("404")){
            modelAndView.setViewName("/error/404");
            return modelAndView;
        }
        Map<String, Object> stringObjectMap = getErrorAttributes(request, true);
        modelAndView.addObject("stackTrace", stringObjectMap.get("trace").toString());
        modelAndView.setViewName("/error/error");
        return modelAndView;
    }
    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));
        Map<String, Object> bodyRes = new HashMap<String, Object>();
        bodyRes.put("message", "异常拦截");
        bodyRes.put("data", body);
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(bodyRes, status);
    }
}

代码中重写了errorHtml() 和 error()

errorHtml()方法

在该方法中,我做了一个404错误和非404错误的判断,如果是404错误,则会返回一个定制化的页面,只有一张图片。如果是非404错误,则会返回相关的错误信息。在templates中建立error文件夹,在该文件夹下面放抛出异常的页面

error.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" layout:decorator="layout">
<head>
    <title>Spring Boot ori统一异常拦截</title>
    <script type="text/javascript">
    </script>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        html, body, .container, .navbar{
            height: 100%;
            width: 100%;
            overflow: hidden;
        }
        .navbar{
            margin-left: 20px;
        }
        h1{
            height: 60px;
        }
        h3{
            height: 40px;
        }
        .debugDiv{
            display: flex;
            margin-left: 20px;
            align-content: center;
            margin-bottom: 20px;
        }
        .linkDiv{
            width: 140px;
            height: 60px;
            margin-left: 10px;
            line-height: 60px;
            text-align: center;
        }
        .linkDiv:nth-child(1){
            background-color: cornflowerblue;
        }
        .linkDiv:nth-child(2){
            background-color: aquamarine;
        }
        .linkDiv:nth-child(3){
            background-color: chocolate;
        }
        .trace{
            width: 80%;
            height: calc(100% - 400px);
            overflow: auto !important;
            border: 2px solid cornflowerblue;
            padding: 5px;
        }
        .trace::-webkit-scrollbar{
            width: 10px;
        }
        .trace::-webkit-scrollbar-track{
            background-color: cornflowerblue;
            /*-webkit-border-radius: 2em;*/
            /*-moz-border-radius: 2em;*/
            /*border-radius: 2em;*/
        }
        .trace::-webkit-scrollbar-thumb{
            background-color: chocolate;
            /*-webkit-border-radius: 2em;*/
            /*-moz-border-radius: 2em;*/
            /*border-radius: 2em;*/
        }
    </style>
</head>
<body>
    <div class="container" layout:fragment="content" th:remove="tag">
        <div class="navbar">
            <h1 th:text="'ori 系统异常统一处理'"></h1>
            <h3 th:text="'时间:'+${timestamp}"></h3>
            <h3 th:text="'状态码:'+${status}"></h3>
            <h3 th:text="'错误状态:'+${error}"></h3>
            <h3 th:text="'地址:'+${path}"></h3>
            <h3>调试</h3>
            <div class="debugDiv">
 
                <div class="linkDiv">
                    <a th:href="@{'https://www.google.com/webhp?hl=zh-CN#safe=strict&hl=zh-CN&q='+${error}}"
                       class="btn btn-default btn-lg" target="_blank" id="Google">Google</a>
                </div>
 
                <div class="linkDiv">
                    <a th:href="@{'https://www.baidu.com/s?wd='+${error}}"
                       class="btn btn-default btn-lg" target="_blank" id="Baidu">Baidu</a>
                </div>
 
                <div class="linkDiv">
                    <a th:href="@{'http://stackoverflow.com/search?q='+${error}}"
                       class="btn btn-default btn-lg"  target="_blank" id="StackOverFlow">StackOverFlow</a>
                </div>
            </div>
 
            <h3>异常堆栈跟踪日志StackTrace</h3>
            <div class="trace" th:each="line:${stackTrace}">
                <div th:text="${line}"></div>
            </div>
        </div>
    </div>
</body>
</html>

404页面

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" layout:decorator="layout">
<head>
    <title>Spring Boot ori统一异常拦截</title>
    <script type="text/javascript">
    </script>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        html, body, .container{
            height: 100%;
            width: 100%;
            overflow: hidden;
        }
        img{
            height: 100%;
            width: 100%;
        }

    </style>
</head>
<body>
    <div class="container">
        <img src="/404.png"/>
    </div>
</body>
</html>

其中404.html页面引入了一张图片,图片放在static下面。整个目录如下

springboot全局请求拦截 springboot全局拦截header_后端_07

这样就完成了ajax请求的异常拦截

springboot全局请求拦截 springboot全局拦截header_springboot全局请求拦截_08

springboot全局请求拦截 springboot全局拦截header_spring_09

error()

error()方法里面是对非ajax请求的处理,例如使用postman请求

springboot全局请求拦截 springboot全局拦截header_后端_10

至此,springboot默认全局异常处理就完成了。这种方式不仅可以获取到异常的接口路径、错误状态、错误代码、还可以获取到堆栈日志等信息。