天天看点

自定义错误页

前言

利用@ControllerAdvice和@ExceptionHandler进行全局异常处理的时候智能处理应用级别的异常,容器级别异常是处理不了的,例如filter中抛出异常。

在Spring boot中,默认情况下,如果用户发起请求时出现了404错误,Spring boot 会提供一个默认的页面展示给用户。

自定义错误页

Spring boot在返回错误信息的时候,不一定返回html页面,而是根据实际情况返回HTML页面或者是一段json。

1 Spring boot默认配置

spring boot默认是由 BasicErrorController类来处理的,该类有两个核心方法

@RequestMapping( produces = {"text/html"})
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
   HttpStatus status = this.getStatus(request);
   Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
   response.setStatus(status.value());
   // 重点
   ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
   return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}

@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
    HttpStatus status = this.getStatus(request);
    return new ResponseEntity(body, status);
}
           

errorHtml 返回 错误HTML页面

error 返回错误JSON

注意

返回html还是json,就要看请求头的Accept参数。

errorHtml方法 ==》 resolveErrorView方法 获取一个错误视图 ModelAndView

resolveErrorView方法属于 DefaultErrorViewResolver 类(Spring Boot 默认的错误信息视图解析器)

private static final Map<Series, String> SERIES_VIEWS;
	
// 初始化 SERIES_VIEWS
static {
       Map<Series, String> views = new EnumMap(Series.class);
       views.put(Series.CLIENT_ERROR, "4xx");
       views.put(Series.SERVER_ERROR, "5xx");
       // 产生一个只读的map
       SERIES_VIEWS = Collections.unmodifiableMap(views);
}

// viewName 其实是httpStatus
private ModelAndView resolve(String viewName, Map<String, Object> model) {
	// 视图定义在error 文件夹下
    String errorViewName = "error/" + viewName;
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
    return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
           

从上述源码可以看出 Spring boot默认是在error目录下查找4xx、5xx的文件作为错误视图(需继续验证),如果找不到的话就会回到errorHtml方法中,使用error作为默认的错误页面视图名(如果配置了视图层,就会去templates下找error.html)。

2 默认实现

2.1 静态页面

不需要向用户展示详细的错误信息,那么就可以把错误信息定义成静态页面,直接在resources/static/error 创建错误展示页面。

命名规则

  1. 4xx.html 、5xx.html
  2. 使用响应码命名文件

2.2 动态页面

可以动态的展示更多的错误信息。在/resources/templates/error/目录下创建错误展示页。4xx.html 、5xx.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>错误页</title>
</head>
<body>
    <table border="1">
        <tr>
            <td>custommsg</td>
            <td th:text="${custommsg}"></td>
        </tr>
        <tr>
            <td>timestamp</td>
            <td th:text="${timestamp}"></td>
        </tr>
        <tr>
            <td>status</td>
            <td th:text="${status}"></td>
        </tr>
        <tr>
            <td>error</td>
            <td th:text="${error}"></td>
        </tr>
        <tr>
            <td>message</td>
            <td th:text="${message}"></td>
        </tr>
        <tr>
            <td>path</td>
            <td th:text="${path}"></td>
        </tr>
    </table>
</body>
</html>
           

后台会自动包装 timestamp , status , error , message , path 这五个属性,errorHtml 或者 error 方法中都是通过getErrorAttributes 方法获得这五个属性的。

响应吗html页面优先级 高于 4xx.html

动态页面的优先级高于静态页面

3 自定义error数据

在上一节里,了解到Spring boot返回的Error信息一共有5条信息,都是通过getErrorAttributes 方法获得的。最终会调用到中的此方法

DefaultErrorAttributes类 ⇒ getErrorAttributes 方法

DefaultErrorAttributes类 ⇒ ErrorMvcAutoConfiguration 类如果没有就会创建

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

自定义数据只需要自己编写一个ErrorAttributes类。继承DefaultErrorAttributes即可。

@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String,Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
        errorAttributes.put("custommsg","出错啦");
        //errorAttributes.remove("error");
        return errorAttributes;
    }
}
           

4 自定义Error视图

ErrorMvcAutoConfiguration 不仅创建了DefaultErrorAttributes类,还创建了DefaultErrorViewResolver 类

@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
           

自定义视图就要提供自定义的ErrorViewResolver

@Component
public class MyErrorViewResolver implements ErrorViewResolver {
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView mv = new ModelAndView("errorPage");
        mv.addObject("custommsg","出错了");
        mv.addAllObjects(model);
        return mv;
    }
}
           

接着前台进行错误展示的视图就应该变成自定义的errorPage。无论发生的是4xx错误还是5xx错误都会进入errorPage.html页面。

5 完全自定义

前面的自定义方式都是针对BasicErrorController中error方法和errorHtml方法中替换ErrorViewResolver(视图),ErrorAttributes(数据)来实现自定义。但其实BasicErrorController 也是由ErrorMvcAutoConfiguration 默认生成的。

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

完全自定义需要提供自定义的Errorcontroller , 一种是实现Errorcontroller接口,另一种是直接继承BasicErrorController类。因为BasicErrorController的功能更加完善,所以选择继承BasicErrorController。

@Controller
public class MyErrorController extends BasicErrorController {
    @Autowired
    public MyErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, serverProperties.getError(), errorViewResolvers);
    }

    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        //状态码
        HttpStatus status = getStatus(request);
        // 得到基本的五个 error 信息
        Map<String,Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("custommsg","出错了");
        ModelAndView modelAndView = new ModelAndView("myErrorPage",model,status);
        return modelAndView;
    }

    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String,Object> body = getErrorAttributes(request,isIncludeStackTrace(request,MediaType.ALL));
        body.put("custommsg","出错啦");
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(body,status);
    }
}
           

摘抄自 《spring boot 全栈开发实战》部分自己理解

下一篇: 2021_03_28

继续阅读