springboot 自定义错误页面

自定义错误页面

背景:当我们访问应用程序不存在的接口路径或者参数传递不规范时,springboot 默认提示 如下页面
在这里插入图片描述
该页面对用户不友好,我们可以自定义展示错误页来改善。

优化后的简洁效果,可对 html 页面进一步美化,这里只说明修改默认错误页方法。
在这里插入图片描述

demo地址

https://gitee.com/stormlong/springboot-errorpage

官方文档

https://docs.spring.io/spring-boot/docs/2.7.18/reference/html/web.html#web.servlet.spring-mvc.error-handling.error-pages
在这里插入图片描述
在这里插入图片描述

静态页面

resources\public\error
不引入任何模板引擎时,在这个目录下寻找
且文件名和错误状态码保持一致

动态页面

resources\templates\error
引入模板引擎时,在这个目录下寻找
例 :

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

且文件名和错误状态码保持一致

优先级

html 文件命名也可以为 4xx 或者是 5xx ,这里的 xx 代表了 404, 401等异常错误

不引入任何模板引擎时,优先具体的错误码页面,然后是 4xx页面,即

  1. \public\error\404.html
  2. \public\error\4xx.html

引入模板引擎时,优先级

  1. \templates\error 目录下的 404.html
  2. \public\error 目录下的 404.html
  3. \templates\error 目录下的 4xx.html
  4. \public\error 目录下的 4xx.html

没有任何错误页面时,默认来到 SpringBoot 默认的错误提示页面

总结:优先具体的错误码页面,然后动态目录下的 4xx 页面
在这里插入图片描述

原理解析

处理异常类

在spring boot中,我们可以找到 BasicErrorController,这个类主要用来处理异常

@Controller
//如果没有配置server.error.path就去error.path找,如果没有配置默认路径为/error
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    ...
}

即我们可以通过 server.error.path 来定义错误的目录,缺省则使用默认 /error 目录

server:
  error:
    path: /error

默认 yaml 配置

server:
  port: 8080
  error:
    path: /error

spring:
  thymeleaf:
    cache: false
    # 以下均为默认配置 可从该类 ThymeleafProperties 下看到 
    prefix: classpath:/templates/
    suffix: .html
    mode: html
    encoding: UTF-8
    servlet:
      content-type: text/html

返回错误信息

SpringBoot默认返回的错误信息,通过DefaultErrorAttruites类可以找到

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.web.servlet.error;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

@Order(-2147483648)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    private static final String ERROR_INTERNAL_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";

    public DefaultErrorAttributes() {
    }

    public int getOrder() {
        return -2147483648;
    }

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        this.storeErrorAttributes(request, ex);
        return null;
    }

    private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
        request.setAttribute(ERROR_INTERNAL_ATTRIBUTE, ex);
    }

    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = this.getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
        if (!options.isIncluded(Include.EXCEPTION)) {
            errorAttributes.remove("exception");
        }

        if (!options.isIncluded(Include.STACK_TRACE)) {
            errorAttributes.remove("trace");
        }

        if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
            errorAttributes.remove("message");
        }

        if (!options.isIncluded(Include.BINDING_ERRORS)) {
            errorAttributes.remove("errors");
        }

        return errorAttributes;
    }

    private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
        } else {
            errorAttributes.put("status", status);

            try {
                errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
            } catch (Exception var5) {
                errorAttributes.put("error", "Http Status " + status);
            }

        }
    }

    private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {
        Throwable error = this.getError(webRequest);
        if (error != null) {
            while(true) {
                if (!(error instanceof ServletException) || error.getCause() == null) {
                    errorAttributes.put("exception", error.getClass().getName());
                    if (includeStackTrace) {
                        this.addStackTrace(errorAttributes, error);
                    }
                    break;
                }

                error = error.getCause();
            }
        }

        this.addErrorMessage(errorAttributes, webRequest, error);
    }

    private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
        BindingResult result = this.extractBindingResult(error);
        if (result == null) {
            this.addExceptionErrorMessage(errorAttributes, webRequest, error);
        } else {
            this.addBindingResultErrorMessage(errorAttributes, result);
        }

    }

    private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
        errorAttributes.put("message", this.getMessage(webRequest, error));
    }

    protected String getMessage(WebRequest webRequest, Throwable error) {
        Object message = this.getAttribute(webRequest, "javax.servlet.error.message");
        if (!ObjectUtils.isEmpty(message)) {
            return message.toString();
        } else {
            return error != null && StringUtils.hasLength(error.getMessage()) ? error.getMessage() : "No message available";
        }
    }

    private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result) {
        errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. Error count: " + result.getErrorCount());
        errorAttributes.put("errors", result.getAllErrors());
    }

    private BindingResult extractBindingResult(Throwable error) {
        return error instanceof BindingResult ? (BindingResult)error : null;
    }

    private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
        StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        errorAttributes.put("trace", stackTrace.toString());
    }

    private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        String path = (String)this.getAttribute(requestAttributes, "javax.servlet.error.request_uri");
        if (path != null) {
            errorAttributes.put("path", path);
        }

    }

    public Throwable getError(WebRequest webRequest) {
        Throwable exception = (Throwable)this.getAttribute(webRequest, ERROR_INTERNAL_ATTRIBUTE);
        if (exception == null) {
            exception = (Throwable)this.getAttribute(webRequest, "javax.servlet.error.exception");
        }

        webRequest.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, exception, 0);
        return exception;
    }

    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return requestAttributes.getAttribute(name, 0);
    }
}

自定义扩展返回

如果要想自定义扩展返回的信息,我们可以自定义一个类来继承这个类,代码如下:

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.HashMap;
import java.util.Map;

@Component
public class CustomDefaultErrorAttribute extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> map = new HashMap<>();
        map.put("compay", "深证腾讯计算公司");
        //调用父类来添加之前Spring的错误信息
        map.putAll(super.getErrorAttributes(webRequest, options));
        return map;
    }

}

页面获取代码如下

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>动态404错误</title>
</head>
<body>
<h1>this is 404 page :(</h1>
<h2>timestamp:[[${timestamp}]]</h2>
<h2>status:[[${status}]]</h2>
<h2>path:[[${path}]]</h2>
<h2>error:[[${error}]]</h2>
<h2>message:[[${message}]]</h2>
<h2>exception:[[${exception}]]</h2>
<h2>trace:[[${trace}]]</h2>
<h2>compay:[[${compay}]]</h2>
</body>
</html>

tomcat 自定义错误页面(扩展)

官方文档:

https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Error_Report_Valve

tomcat-404错误,使用自定义页面屏蔽敏感信息

  1. 在项目中新增404页面,例如:/resource/templates/errorpage/400.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Error</title>
</head>
<body>
<h1>400 Bad Request</h1>
</body>
</html>
  1. 在server.xml配置文件里进行配置 配置节点下新增 部分

    https://tomcat.apache.org/tomcat-9.0-doc/config/host.html

    https://tomcat.apache.org/tomcat-9.0-doc/config/context.html

<Host appBase="/app/webapps">
    <Context path="/abc/def"></Context>
	<Valve className="org.apache.catalina.valves.ErrorReportValve" errorCode.404="/app/webapps/abc/def/WEB-INF/classes/templates/errorpage/400.html" showReport="false" showServerInfo="false" />
</Host>

访问不成功时可尝试 abc/ 替换成 abc#

/app/webapps/abc#def/WEB-INF/classes/templates/errorpage/400.html

配置说明

showReport:如果设置为false,则不会在HTML响应中返回错误报告。默认值:true

showServerInfo:如果设置为false,则不会在HTML响应中返回服务器版本。默认值:true

errorCode.xxx: 要为xxx表示的HTTP错误代码返回的UTF-8编码的HTML文件的位置。例如,errorCode.404指定HTTP 404错误返回的文件。位置可以是相对的或绝对的。如果是相对的,则必须是相对于 $CATALINA_BASE的。

className要使用的实现的Java类名。必须将其设置为 org.apache.catalina.valves.ErrorReportValve 以使用默认的错误报告值。

appBase: 此虚拟主机的应用程序基目录。这是一个目录的路径名,该目录可能包含要在此虚拟主机上部署的Web应用程序。如果未指定,则将使用默认的 webapps。

path: 此Web应用程序的上下文路径,它与每个请求URI的开头相匹配,以选择要处理的适当Web应用程序。特定主机内的所有上下文路径都必须是唯一的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值