欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springboot(11)——异常处理和自定义错误页面

程序员文章站 2022-08-08 12:10:57
要处理程序发生的异常,首先需要知道异常来自哪里?1.前端错误的的请求路径,会使得程序发生4xx错误,最常见的就是404,Springboot默认当发生这种错误的请求路径,pc端响应的页面如下 ......

1.异常来源

要处理程序发生的异常,首先需要知道异常来自哪里?

1.前端错误的的请求路径,会使得程序发生4xx错误,最常见的就是404,springboot默认当发生这种错误的请求路径,pc端响应的页面如下

springboot(11)——异常处理和自定义错误页面

如果是移动端(手机端)将会响应json格式的数据,如下

springboot(11)——异常处理和自定义错误页面

2.springboot异常处理原理

为什么我们请求错误的路径,boot会给我们返回一个上面错误页面或者json格式数据呢?原理是怎样的呢?

springboot项目启动之后,执行有@springbootapplication注解的启动类的main方法,通过@enableautoconfiguration加载

springbootautoconfiguration.jar包下的meta-inf/spring.factories中的所有配置类(这些配置类加载之后,会将每个配置类里面的组件注入容器然后使用)其中一个自动配置

springboot(11)——异常处理和自定义错误页面

errormvcautoconfiguration,通过代码可以看到用到了以下四个组件

defaulterrorattributes、basicerrorcontroller、errorpagecustomizer、defaulterrorviewresolver

springboot(11)——异常处理和自定义错误页面

其他三个基本类似,当出现4xx或者5xx等错误时,errorpagecustomizer就会生效,this.properties.geterror().getpath())并来到/error请求,核心代码

//errorpagecustomizer
@value("${error.path:/error}")
    private string path = "/error";

而这个/error请求再由basicerrorcontroller处理,basicerrorcontroller是一个controller,其中里面有两种处理方法,一种是html形式,

一种是json格式。其中访问者的信息可以从geterrorattributes从获取。defaulterrorattributes是errorattributes的实现类。

关键代码

@requestmapping(produces = "text/html") //html
    public modelandview errorhtml(httpservletrequest request,
            httpservletresponse response) {
        httpstatus status = getstatus(request);
        map<string, object> model = collections.unmodifiablemap(geterrorattributes(
                request, isincludestacktrace(request, mediatype.text_html)));
        response.setstatus(status.value());
        modelandview modelandview = resolveerrorview(request, response, status, model);
        return (modelandview == null ? new modelandview("error", model) : modelandview);
    }

    @requestmapping
    @responsebody //json
    public responseentity<map<string, object>> error(httpservletrequest request) {
        map<string, object> body = geterrorattributes(request,
                isincludestacktrace(request, mediatype.all));
        httpstatus status = getstatus(request);
        return new responseentity<>(body, status);
    }

当为html模式时,就会构建一个resolveerrorview类,而resolvererrorview继续调用errorviewresolver。关键代码

protected modelandview resolveerrorview(httpservletrequest request,
            httpservletresponse response, httpstatus status, map<string, object> model) {
        for (errorviewresolver resolver : this.errorviewresolvers) {
            modelandview modelandview = resolver.resolveerrorview(request, status, model);
            if (modelandview != null) {
                return modelandview;
            }
        }
        return null;
    }

在我们没有做自定义配置时,errorviewresolver就会指向defaulterrorviewresolver。

static {
        //可以用4xx,5xx文件名来统一匹配错误,但是会以精确优先的原则
        map<series, string> views = new enummap<>(series.class);
        views.put(series.client_error, "4xx");
        views.put(series.server_error, "5xx");
        series_views = collections.unmodifiablemap(views);
    }

 @override
    public modelandview resolveerrorview(httpservletrequest request, httpstatus status,
                                         map<string, object> model) {
        modelandview modelandview = resolve(string.valueof(status), model);
        if (modelandview == null && series_views.containskey(status.series())) {
            modelandview = resolve(series_views.get(status.series()), model);
        }
        return modelandview;
    }

    private modelandview resolve(string viewname, map<string, object> model) {
        //将错误代码拼接到error后
        string errorviewname = "error/" + viewname;
        templateavailabilityprovider provider = this.templateavailabilityproviders
                .getprovider(errorviewname, this.applicationcontext);
        //如果模版引擎可用就让模版引擎进行解析如:template/error/404
        if (provider != null) {
            return new modelandview(errorviewname, model);
        }
        //如果模版引擎不可用,就在静态资源文件夹下找资源文件,error/404
        return resolveresource(errorviewname, model);
    }

3.如何定制错误、异常响应

明白了boot处理异常机制,我们如何自定义异常响应规则呢?

第一种:pc端返回静态错误页面,手机端返回boot默认的json数据

如果项目中有模板引擎(jsp,thmeleaf,freemarker)的情况下,可以将错误页面命名为状态码.html放在模板引擎文件夹下的error文件夹下,

发生异常,不管是前端请求还是后端程序错误会来到对应的错误页面。可以将错误页面命名为4xx和5xx匹配所有的错误,

但是优先返回精确状态码.html页面;并且在模板引擎页面可以获取如下相关信息

springboot(11)——异常处理和自定义错误页面

这里模版引擎使用thmeleaf

springboot(11)——异常处理和自定义错误页面

4xx代码

<!doctype html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <title th:text="'状态码:'+${status}"></title>
</head>
<body>
< img src="../images/404.jpg" style="width: 40%;">
<h1 th:text="'时间:'+${timestamp}"></h1>
<h1 th:text="'错误提示:'+${error}"></h1>
<h1 th:text="'异常对象:'+${exception}"></h1>
<h1 th:text="'异常信息:'+${message}"></h1>
</body>
</html>
5xx代码
<!doctype html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <title th:text="'状态码:'+${status}"></title>
</head>
<body>
<h2>500</h2>
<h1 th:text="'时间:'+${timestamp}"></h1>
<h1 th:text="'错误提示:'+${error}"></h1>
<h1 th:text="'异常对象:'+${exception}"></h1>
<h1 th:text="'异常信息:'+${message}"></h1>
</body>
</html>
我们请求一个错误的地址路径

springboot(11)——异常处理和自定义错误页面

我们在程序代码中人为制造一个异常,请求响应

springboot(11)——异常处理和自定义错误页面

上面是有模版引擎的情况下处理错误以及异常的方式,

如果项目中没有模板引擎,(模板引擎找不到这个错误页面),静态资源文件夹static下找对应的4xx或者5xx或者更精确的错误页面。但是如果不用模板引擎,页面不能获取上面说的页面信息;

springboot(11)——异常处理和自定义错误页面

上面两种方式使用手机访问返回都是boot默认的json数据

第二种:pc端返回动态的页面 ,手机端返回动态json数据

上面第一种可以轻松的的处理异常,只需在指定的路径下放静态页面(无模版引擎的情况)或者携带相关信息的页面(有模版引擎),

缺点就是不能在页面携带我们想要展示的数据,比如当我们程序某处放生异常,我们要返回我们自己提示的错误信息。这种异常如果处理呢?

默认情况下,在 spring boot 中,所有的异常数据其实就是第一种所展示出来的 5几条数据,这些数据定义在 org.springframework.boot.web.reactive.error.defaulterrorattributes 类中,具体定义在 geterrorattributes 方法中 :核心代码如下

@override
public map<string, object> geterrorattributes(serverrequest request,
                boolean includestacktrace) {
        map<string, object> errorattributes = new linkedhashmap<>();
        errorattributes.put("timestamp", new date());
        errorattributes.put("path", request.path());
        throwable error = geterror(request);
        httpstatus errorstatus = determinehttpstatus(error);
        errorattributes.put("status", errorstatus.value());
        errorattributes.put("error", errorstatus.getreasonphrase());
        errorattributes.put("message", determinemessage(error));
        handleexception(errorattributes, determineexception(error), includestacktrace);
        return errorattributes;
}

defaulterrorattributes 类本身则是在 org.springframework.boot.autoconfigure.web.servlet.error.errormvcautoconfiguration 异常自动配置类中定义的,

如果开发者没有自己提供一个 errorattributes 的实例的话,那么 spring boot 将自动提供一个 errorattributes 的实例,也就是 defaulterrorattributes 。

基于此 ,开发者自定义 errorattributes 有两种方式 实现自定义数据:

    1.直接实现 errorattributes 接口
    2.继承 defaulterrorattributes(推荐),因为 defaulterrorattributes 中对异常数据的处理已经完成,开发者可以直接使用。

package com.javayihao.top.config;

import org.springframework.boot.web.servlet.error.defaulterrorattributes;
import org.springframework.stereotype.component;
import org.springframework.web.context.request.webrequest;

import java.util.map;

@component
public class myerrorattributes  extends defaulterrorattributes {
    @override
    public map<string, object> geterrorattributes(webrequest webrequest, boolean includestacktrace) {
        map<string, object> map = super.geterrorattributes(webrequest, includestacktrace);
        if ((integer)map.get("status") == 500) {
            //这里根据自己需求设置
            map.put("message", "服务器内部错误!");
        }
        if ((integer)map.get("status") == 404) {
            map.put("message", "路径不存在!");
        }
        return map;
    }
}

我们服务器访问 错误路径

springboot(11)——异常处理和自定义错误页面

客户端响应

springboot(11)——异常处理和自定义错误页面

访问有异常的的控制器

springboot(11)——异常处理和自定义错误页面

客户端响应

springboot(11)——异常处理和自定义错误页面

当然上面我可以在程序任意位置抛出异常,使用全局异常处理器处理

自定义异常

springboot(11)——异常处理和自定义错误页面

全局异常处理器

@controlleradvice
public class myexceptionhandler {
    @exceptionhandler(myexception.class)
    public string jsonerrorhandler(httpservletrequest request, exception e) {
        map<string, object> map = new hashmap<>();
        request.setattribute("java.servlet.error.status_code", 500);
        map.put("code", -1);
        map.put("msg", e.getmessage());
        request.setattribute("ext", map);
        //转发到error
        return "forward:/error";
    }
}

 自定义errorattributes

@component
public class myerrorattributes extends defaulterrorattributes {
    //返回的map就是页面或者json能获取的所有字段
    @override
    public map<string, object> geterrorattributes(webrequest webrequest, boolean includestacktrace) {
        map<string, object> map = super.geterrorattributes(webrequest, includestacktrace);
        //可以额外添加内容
        map.put("company", "javayihao");
        //取出异常处理器中的携带的数据
        map<string, object> ext = (map<string, object>) webrequest.getattribute("ext", 0);//传入0代表从request中获取
        map.put("ext", ext);
        return map;
    }
}

 

第三种:pc端返回动态json数据,手机端也返回动态json数据

这种方式主要是针对前后端分离的项目,我们自定义一个异常,在程序中用于抛出

springboot(11)——异常处理和自定义错误页面

定义一个返回结果对象(也可以不用定义,直接使用map)存储异常信息

springboot(11)——异常处理和自定义错误页面

定义一个全局异常处理器

/*controlleradvice用来配置需要进行异常处理的包和注解类型,
 比如@controlleradvice(annotations = restcontroller.class)
 只有类标有rescontrolle才会被拦截
 */
@controlleradvice
public class myexceptionhandler {
    //自己创建的异常按照自己编写的信息返回即可
    @exceptionhandler(value = myexception.class)
    @responsebody
    public errorinfo<string> errorinfo(httpservletrequest req, myexception e) {
        errorinfo<string> r = new errorinfo<>();
        r.setcode(errorinfo.error);
        r.setmessage(e.getmessage());
        r.setdata("测试数据");
        r.seturl(req.getrequesturl().tostring());
        return r;
    }
    //系统异常时返回的异常编号为 -1 ,返回的异常信息为 “系统正在维护”;不能将原始的异常信息返回
    @exceptionhandler(value = exception.class)
    @responsebody
    public errorinfo<string> errorinfo(httpservletrequest req, exception e) {
        errorinfo<string> r = new errorinfo<>();
        r.setcode(errorinfo.error);
        r.setmessage("系统维护中");
        return r;
    }
}

springboot(11)——异常处理和自定义错误页面