SpringBoot 默认错误处理机制源码窥探

       当项目代码执行错误(5XX)、找不到页面(4xx)或其他错误时,SpringBoot 自动将请求转发到 /error 下进行处理,所以每次出现错误我们都能得到这样一个页面。相信很多人都跟我一样,看到这个页面就想到是页面找不到(404)。但是却没有注意到 Spring 给我们的这么多提示。无意中发现这个,我们访问的路径是 /logins,但是错误提示确实 /error找不到映射。这是为什么呢?会不会是 SpringBoot 默认的一种错误处理方式?

       找了几篇博客,看了几个视频,终于找到原因,下面通过源码简单探索一下SpringBoot默认错误处理的机制。

       1. SpringBoot 错误处理机制是区分浏览器和客户端的,如果是浏览器访问,默认返回错误页面,如果是客户端访问默认是返回错误的JSON数据。 通过PostMan访问这个页面可以得到这样一串JSON数据

{
    "timestamp": 1567084756940,
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/logins"
}

      2. 错误处理机制的源码实现,首先可以找到相关的自动配置类:ErrorMvcConfiguration.java(不要问我怎么知道的),在这个自动配置类里我们很容易在 101 行找到一个很熟悉的东西-----BasicErrorController,SpringBoot默认注入了这个错误处理的控制器组件。

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
	return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
			this.errorViewResolvers);
}

      3. 在这个BasicErrorController可以发现

      1)通过类上的@RequestMapping注解可以知道,SpringBoot 默认的错误处理映射路径 /error

      2)从 errorHtml 方法上面的注解 @RequestMapping(produces = "text/html") 可以知道这个方法是返回错误页面的;

           从 error方法上面的注解 @ResponseBody 可以知道这个方法是返回错误JSON数据的;

      3)在处理错误请求时,调用父类的 getErrorAttributes 方法来获取错误信息,调用父类的  resolveErrorView 解析错误视图

package org.springframework.boot.autoconfigure.web;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.autoconfigure.web.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.autoconfigure.web.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
// 此处会从配置文件中读取默认的错误映射路径 server.error.path
// 如果没有配置 server.error.path,则会继续查找 error.path 有没有配置
// 如果 error.path 也没有配置,会使用默认映射路径的路径 /error
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes,
                                ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties,
                Collections.<ErrorViewResolver>emptyList());
    }

    public BasicErrorController(ErrorAttributes errorAttributes,
                                ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    @Override
    public String getErrorPath() {
        return this.errorProperties.getPath();
    }

    /**
     * 响应错误页面的方法
     */
    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
                                  HttpServletResponse response) {
        // 获取 Http 状态码
        HttpStatus status = getStatus(request);
        // 进入 getErrorAttributes 方法获取错误信息,我们在页面上看到的错误信息就是在这获取的,这个方法继承自父类 AbstractErrorController
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        // 进入 resolveErrorView 方法解析错误视图,这个方法继承自父类 AbstractErrorController
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        // 如果解析视图失败,则获取默认的空白视图"error",这是SpringBoot默认提供的视图,在文章的第7点中会讲到
        return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
    }

    /**
     * 响应错误JSON数据的方法
     */
    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        // 获取错误信息
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        // 获取 Http 状态码
        HttpStatus status = getStatus(request);
        // 直接返回错误数据
        return new ResponseEntity<Map<String, Object>>(body, status);
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request,
                                          MediaType produces) {
        IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }

    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }

}

      4. 进入 AbstractErrorController 找到 getErrorAttributes  和  resolveErrorView 两个方法。在 getErrorAttributes 方法中又调用了 ErrorAttributes 接口 的 getErrorAttributes 方法来获取错误信息。在 resolveErrorView  中:遍历容器中所有的 ErrorViewResolver 来解析视图,如果解析得到,则返回视图,否则返回null 。

protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
                                                     boolean includeStackTrace) {
    RequestAttributes requestAttributes = new ServletRequestAttributes(request);

    // 调用 ErrorAttributes 的 getErrorAttributes 方法获取错误参数
    // ErrorAttributes 是一个接口,进入他的实现类的 getErrorAttributes 方法
    return this.errorAttributes.getErrorAttributes(requestAttributes,
            includeStackTrace);
}

/**
 * Resolve any specific error views. By default this method delegates to
 * {@link ErrorViewResolver ErrorViewResolvers}.
 * @param request the request
 * @param response the response
 * @param status HTTP状态码
 * @param model 错误信息
 * @return a specific {@link ModelAndView} or {@code null} if the default should be
 * used
 * @since 1.4.0
 */
protected ModelAndView resolveErrorView(HttpServletRequest request,
                                            HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
    // 遍历 Spring 容器中所有 ErrorViewResolver 错误视图解析器
    for (ErrorViewResolver resolver : this.errorViewResolvers) {
        // 调用 ErrorViewResolver 视图解析器 的 resolveErrorView 方法解析视图
        // ErrorViewResolver 是一个接口,进入他的实现类的 resolveErrorView 方法
        ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
        if (modelAndView != null) {
            return modelAndView;
        }
    }
    return null;
}

      5. 进入 ErrorAttributes 类的 getErrorAttributes 方法。ErrorAttributes 主要用于解析错误信息。

package org.springframework.boot.autoconfigure.web;

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.autoconfigure.web.ErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

/**
 * Default implementation of {@link ErrorAttributes}. Provides the following attributes
 * when possible:
 * <ul>
 * <li>timestamp - The time that the errors were extracted</li>
 * <li>status - The status code</li>
 * <li>error - The error reason</li>
 * <li>exception - The class name of the root exception</li>
 * <li>message - The exception message</li>
 * <li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception
 * <li>trace - The exception stack trace</li>
 * <li>path - The URL path when the exception was raised</li>
 * </ul>
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Stephane Nicoll
 * @since 1.1.0
 * @see ErrorAttributes
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
        implements ErrorAttributes, HandlerExceptionResolver, Ordered {

    private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName()
            + ".ERROR";

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

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

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

     /**
     * 获取的错误信息
     * @param requestAttributes
     * @param includeStackTrace 是否包含错误堆栈详细信息
     */
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
                                                  boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
        // 获取时间戳
        errorAttributes.put("timestamp", new Date());
        // 获取 Http 状态码
        addStatus(errorAttributes, requestAttributes);
        // 获取错误详细信息
        addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
        // 获取错误路径信息
        addPath(errorAttributes, requestAttributes);
        return errorAttributes;
    }

    private void addStatus(Map<String, Object> errorAttributes,
                           RequestAttributes requestAttributes) {
        // 从 requestAttributes 对象中获取 name 为 javax.servlet.error.status_code 的值
        // 可以得出,如果想手动定义错误状态码status的,只需要设置 HttpRequest 中 name 为 javax.servlet.error.status_code 的值
        Integer status = getAttribute(requestAttributes,
                "javax.servlet.error.status_code");
        // 如果没有获取到错误状态,则设置默认的 999 状态码和错误信息 None
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
            return;
        }
        // 获取到错误状态码时,从HttpStatus中获取错误信息
        // HttpStatus 是枚举类型,包含两个属性错误状态码 value 和错误原因 reasonPhrase
        errorAttributes.put("status", status);
        try {
            errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
        }
        catch (Exception ex) {
            // 找到对应值的 reasonPhrase 则会发生异常,获取异常信息
            errorAttributes.put("error", "Http Status " + status);
        }
    }

    private void addErrorDetails(Map<String, Object> errorAttributes,
                                 RequestAttributes requestAttributes, boolean includeStackTrace) {
        // 从 requestAttributes 获取异常对象
        Throwable error = getError(requestAttributes);
        if (error != null) {
            while (error instanceof ServletException && error.getCause() != null) {
                error = ((ServletException) error).getCause();
            }
            // 获取异常类型
            errorAttributes.put("exception", error.getClass().getName());
            // 获取异常信息
            addErrorMessage(errorAttributes, error);
            // 添加堆栈跟踪信息
            if (includeStackTrace) {
                addStackTrace(errorAttributes, error);
            }
        }
        // 获取 requestAttributes 携带的异常信息
        Object message = getAttribute(requestAttributes, "javax.servlet.error.message");
        // 如果上面获取不到 error 对象,则获取 requestAttributes 携带的错误信息
        if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
                && !(error instanceof BindingResult)) {
            errorAttributes.put("message",
                    StringUtils.isEmpty(message) ? "No message available" : message);
        }
    }

    /**
     * 获取 JSR-303校验的错误信息
     * @param errorAttributes
     * @param error
     */
    private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
        BindingResult result = extractBindingResult(error);
        if (result == null) {
            errorAttributes.put("message", error.getMessage());
            return;
        }
        if (result.getErrorCount() > 0) {
            errorAttributes.put("errors", result.getAllErrors());
            errorAttributes.put("message",
                    "Validation failed for object='" + result.getObjectName()
                            + "'. Error count: " + result.getErrorCount());
        }
        else {
            errorAttributes.put("message", "No errors");
        }
    }

    /**
     * 提取BindingResult对象,BindingResult 包含 JSR-303校验的错误信息
     * @param error 异常对象
     * @return
     */
    private BindingResult extractBindingResult(Throwable error) {
        if (error instanceof BindingResult) {
            return (BindingResult) error;
        }
        if (error instanceof MethodArgumentNotValidException) {
            return ((MethodArgumentNotValidException) error).getBindingResult();
        }
        return null;
    }

    /**
     * 获取异常的堆栈跟踪信息
     * @param errorAttributes 错误信息集合
     * @param error 异常对象
     */
    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());
    }

    /**
     * 获取请求路径
     * @param errorAttributes
     * @param requestAttributes
     */
    private void addPath(Map<String, Object> errorAttributes,
                         RequestAttributes requestAttributes) {
        String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri");
        if (path != null) {
            errorAttributes.put("path", path);
        }
    }

    /**
     * 获取异常信息
     * @param requestAttributes
     * @return
     */
    @Override
    public Throwable getError(RequestAttributes requestAttributes) {
        Throwable exception = getAttribute(requestAttributes, ERROR_ATTRIBUTE);
        if (exception == null) {
            exception = getAttribute(requestAttributes, "javax.servlet.error.exception");
        }
        return exception;
    }

    @SuppressWarnings("unchecked")
    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
    }

}

    6. 进入 ErrorViewResolver 的 resolveErrorView 方法,ErrorViewResolver主要用于解析错误视图。通过这个类,可以通过错误请求的 HttpStatus 错误代码将 template 文件夹下的 error/HttpStatus.html 文件解析成我们想要的视图。如果template下没有对应的文件,SpringBoot 还会去静态资源文件夹下找 对应的 HTML文件。

package cn.edu.zut.rsc.vote.controller;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ErrorViewResolver;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

    private static final Map<Series, String> SERIES_VIEWS;

    static {
        // Series 是一个枚举类型,默认有以下五个值:
        // INFORMATIONAL(1),SUCCESSFUL(2),REDIRECTION(3),CLIENT_ERROR(4),SERVER_ERROR(5);
        // 即 views = {"CLIENT_ERROR": "4xx", "SERVER_ERROR": "5xx"}
        // 也就是所有 1xx 的 Http状态码,Series都将其定义为信息
        //       所有 2xx 的 Http状态码,Series都将其定义为成功
        //       所有 3xx 的 Http状态码,Series都将其定义为转发
        //       所有 4xx 的 Http状态码,Series都将其定义为客户端错误
        //       所有 5xx 的 Http状态码,Series都将其定义为服务端错误

        Map<Series, String> views = new HashMap<Series, String>();
        views.put(Series.CLIENT_ERROR, "4xx");
        views.put(Series.SERVER_ERROR, "5xx");
        SERIES_VIEWS = Collections.unmodifiableMap(views);
    }

    private ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final TemplateAvailabilityProviders templateAvailabilityProviders;

    private int order = Ordered.LOWEST_PRECEDENCE;

    public DefaultErrorViewResolver(ApplicationContext applicationContext,
                                    ResourceProperties resourceProperties) {
        Assert.notNull(applicationContext, "ApplicationContext must not be null");
        Assert.notNull(resourceProperties, "ResourceProperties must not be null");
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.templateAvailabilityProviders = new TemplateAvailabilityProviders(
                applicationContext);
    }

    DefaultErrorViewResolver(ApplicationContext applicationContext,
                             ResourceProperties resourceProperties,
                             TemplateAvailabilityProviders templateAvailabilityProviders) {
        Assert.notNull(applicationContext, "ApplicationContext must not be null");
        Assert.notNull(resourceProperties, "ResourceProperties must not be null");
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.templateAvailabilityProviders = templateAvailabilityProviders;
    }

    /**
     * 1.解析错误视图
     * @param request
     * @param status Http状态码
     * @param model 错误信息
     * @return
     */
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
                                         Map<String, Object> model) {
        // 通过 Http 状态码解析视图
        ModelAndView modelAndView = resolve(String.valueOf(status), model);
        // 查看 HttpStatus.series() 的源码发现就一行代码 HttpStatus.Series.valueOf(this)
        // 而 Series.valueOf() 实际是先获取了一个 HttpStatus 的百位数值,也就是(1,2,3,4,5),再用这数值获取对应的 Series 返回
        // 举个例子:如果是 status 是 404:404 的百位数值是4,则返回 CLIENT_ERROR,500的话:500的百位数值为5,返回 SERVER_ERROR
        // 这里其实是 SpringBoot 提供的一种通配符机制,所有 4xx 和 5xx 状态码 经过这一步操作都转换成"4xx"或"5xx"
        // 再调用 resolve("4xx", model),resolve("5xx", model) 获取对应的视图
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
        }
        return modelAndView;
    }

    /**
     * 2.解析错误视图
     * @param viewName 视图名,这里即 Http 状态码
     * @param model 错误信息
     * @return
     */
    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        // 将 error 和 Http状态码拼接成视图名,如404: error/404,500: error/500
        String errorViewName = "error/" + viewName;
        // 使用模板引擎解析这个视图名,也就是说只要在template目录下存在这样一个页面(error/404.html)
        // 模板引擎就会将这个页面解析成错误视图
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
                .getProvider(errorViewName, this.applicationContext);
        // 如果解析成功则返回对应的视图
        if (provider != null) {
            return new ModelAndView(errorViewName, model);
        }
        // 如果解析失败:没有使用模板引擎或 template 没有对应的 html 文件
        return resolveResource(errorViewName, model);
    }

    /**
     * 3.解析错误视图
     * @param viewName 视图名,这里即 error/HttpStatus
     * @param model 错误信息
     * @return
     */
    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        // 遍历项目静态资源路径
        for (String location : this.resourceProperties.getStaticLocations()) {
            try {
                Resource resource = this.applicationContext.getResource(location);
                // 关联项目静态资源路径下对应的 error/HttpStatus.html
                resource = resource.createRelative(viewName + ".html");
                // 如果该文件存在,获取对应的HtmlResourceView初始化ModelAndView并返回
                if (resource.exists()) {
                    return new ModelAndView(new HtmlResourceView(resource), model);
                }
            }
            catch (Exception ex) {
            }
        }
        // 静态资源目录下没有找到对应的文件则返回null
        return null;
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    private static class HtmlResourceView implements View {

        private Resource resource;

        HtmlResourceView(Resource resource) {
            this.resource = resource;
        }

        @Override
        public String getContentType() {
            return MediaType.TEXT_HTML_VALUE;
        }

        @Override
        public void render(Map<String, ?> model, HttpServletRequest request,
                           HttpServletResponse response) throws Exception {
            response.setContentType(getContentType());
            FileCopyUtils.copy(this.resource.getInputStream(),
                    response.getOutputStream());
        }

    }

}

7. SpringBoot默认提供的空白错误页面,也就是文章最开始的截图。当视图解析失败时,SpringBoot就会返回这样一个默认的空白错误提示页面。

@Configuration
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {

    private final ErrorMvcAutoConfiguration.SpelView defaultErrorView = new ErrorMvcAutoConfiguration.SpelView(
            "<html><body><h1>Whitelabel Error Page</h1>"
                    + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
                    + "<div id='created'>${timestamp}</div>"
                    + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
                    + "<div>${message}</div></body></html>");

    @Bean(name = "error")
    @ConditionalOnMissingBean(name = "error")
    public View defaultErrorView() {
        return this.defaultErrorView;
    }

    // 将空白错误视图加入到 Spring 容器中
    @Bean
    @ConditionalOnMissingBean(BeanNameViewResolver.class)
    public BeanNameViewResolver beanNameViewResolver() {
        BeanNameViewResolver resolver = new BeanNameViewResolver();
        resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
        return resolver;
    }

}

8. 总结

     1)发生错误时,可通过 HttpServletRequest.setAttribute("javax.servlet.error.status_code", status) 来修改 HttpStatus 状态码。

     2)可在配置文件中配置 server.error.path(建议) 或 error.path(已经被废弃了) 来修改默认的 /error 错误转发处理的路径

     3)有模板引擎的情况下,可以在 template 目录下新建 error 文件夹,创建对应错误状态码的Html文件,SpringBoot 会将错误自动解析到对应的错误状态码.html上;

          没有使用模板引擎,在静态资源问价夹 {"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"} 下创建 error 文件夹并在 error 文件夹下创建对应状态码的 Html 文件也可解析得到。

          注意:两种方法都必须先创建 error 文件夹,再在 error 文件夹下创建对应的 Html 文件。有模板引擎时,可以通过模板引擎将错误信息显示到页面上。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值