四、Spring Boot 整合 Web开发(3)

本章概要

  • 自定义错误页

4.5 自定义错误页

@ControllerAdvice 可以根据实际情况返回不同的页面,但是这种异常处理方式一般用来处理应用级别的异常,有一些容器级别的错误就处理不了,例如 Filter 中抛出异常。在Spring Boot 中,默认情况下,如果用户在发起请求时发生了404错误,Spring Boot 会有一个默认的页面展示给用户,如图:
在这里插入图片描述

500 错误也有一个默认的页面,如下:
在这里插入图片描述

事实上,Spring Boot 在返回错误信息时不一定返回HTML页面,而是根据实际情况返回HTML页面或者一段JSON(若开发者发起的 Ajax 请求,则错误信息是一段 JSON),对于开发者而言,这一段HTML或 JSON 都能够自由定制。
Spring Boot 中的错误默认是由 BasicErrorController 类来处理,改类中的核心方法主要有两个:

@RequestMapping(produces = "text/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) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
@ResponseBody
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);
}

其中,errorHtml 方法用来返回错误HTML 页面,error 用来返回错误 JSON ,具体返回的是HTML 还是 JSON ,则要看请求头的 Accept 参数。返回 JSON 的逻辑很简单,不必过多介绍,返回 HTML 的逻辑稍微有点复杂,在errorHtml 方法中,通过调用 resolveErrorView 方法来获取一个错误视图的 ModelAndView 。而 resolveErrorView 方法的调用最终会来到 DefaultErrorViewResolver 类中。
DefaultErrorViewResolver 是 Spring Boot 中默认的错误信息视图解析器,部分源码如下:

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
	private static final Map<Series, String> SERIES_VIEWS;
	static {
		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);
	}
    ...
	private ModelAndView resolve(String viewName, Map<String, Object> model) {
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}

从这一段源码中可以看出,Spring Boot 默认是在 error 目录下查找 4xx 、 5xx 的文件作为错误视图,当找不到时会回到 errorHtml 方法中,然后使用 error 作为默认的错误页面视图名,如果名为error 的视图也找不到,用户就会看到前面提到的 400 、500 错误页。整个错误处理流程大致就是这样的。

4.5.1 简单配置

直接在 resources/static 目录下创建 error 目录,然后在error 目录中创建错误展示页面。错误展示页面的命名规则有两种:一种是 4xx.html 、5xx.html ;另一种是直接使用响应码命名文件,如 404.html、405.html。第二种命名方式划分得更细,当出错时,不同的错误会展示不同的错误页面,如图:
在这里插入图片描述

此时访问不存在的路径时就会展示404.html中的内容,如图
在这里插入图片描述

修改 Controller ,提供一个会抛异常的请求,代码如下:

@GetMapping("/hello")
public void hello(Model model){
    int a = 0/0;
}

访问接口会展示500.html中的内容,如下:
在这里插入图片描述

采用视图模板可以向用户展示更多的错误信息,如使用 HTML 模板,以 Thymeleaf 为例,引入依赖。Thymeleaf 的页面模板默认处于 classpath:/templates/ 目录下,在该目录下创建 error 目录,再创建错误展示页,如下
在这里插入图片描述

由于模板页面展示信息比较灵活,因此可以直接创建 4xx.html、5xx.html 。以4xx.html 页面为例,内容如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>4xx</title>
</head>
<body>
<table border="1">
    <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>

此时 404 错误如下
在这里插入图片描述

500 错误如下:
在这里插入图片描述

注意:

  • 400.html 优先级大于 4xx.html,以此类推
  • 动态页面的优先级高于静态页面,即若 resources/templates 和 resources/static 同时定义了4xx.html 则优先展示 resources/templates/4xx.html

4.5.2 复杂配置

上面的配置仍不够灵活,只能自定义 HTML 页面 ,无法处理 JSON 的定制。Spring Boot 中支持对 Error 信息的深度定制 , 接下来从三个方面介绍深度定制:自定义 Error 数据、自定义 Error 视图以及完全自定义

1. 自定义 Error 数据

Spring Boot 返回的 Error 信息一共有 5 条,分别是 timestamp、status、error、message 、path。在 BasicErrorController 的 errorHtml 方法和 error 方法中,都是通过 getErrorAttributes 方法获取 Error 信息的。该方法最终会调用到 DefaultErrorAttributes 类的 getErrorAttributes 方法,而 DefaultErrorAttributes 类是在 ErrorMvcAutoConfiguration 中默认提供的。ErrorMvcAutoConfiguration 类的 errorAttributes 的方法源码如下:

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

从源码中可以看出,当系统没有提供 ErrorAttributes 时才会采用 DefaultErrorAttributes。因此自定义错误提示时,只需要自己提供一个 ErrorAttributes 即可,而 DefaultErrorAttributes 是 ErrorAttributes 的子类,因此只需要继承 DefaultErrorAttributes 即可,代码如下:

@Component
public class MyErrorAttribute extends DefaultErrorAttributes {

    @Override
    public Map<String,Object> getErrorAttributes (WebRequest webRequest,boolean b){
        Map<String, Object> errorMap = super.getErrorAttributes(webRequest,b);
        errorMap.put("custommsg","出错啦!");
        errorMap.remove("error");
        return errorMap;
    }

}

代码解释:

  • 自定义 MyErrorAttribute 继承自 DefaultErrorAttributes,重写 DefaultErrorAttributes 的 getErrorAttributes 方法。MyErrorAttribute 类添加 @Component 注解,该类将被注册到 Spring Boot 容器中
  • 通过 super.getErrorAttributes(webRequest,b); 获取Spring Boot 获取默认提供的错误信息,然后在此基础上添加 Error 信息或者移除 Error 信息

修改模板,如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>4xx</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>

此时 400 的访问结果如下:
在这里插入图片描述

如果通过Postman 等工具来发起这个请求,那么返回的 JSON 数据也是如此
在这里插入图片描述

2. 自定义 Error 视图

Error 视图是展示给用户的页面,在 BasicErrorController 的 errorHtml 方法中调用 resolveErrorView 方法获取一个 ModelAndView 实例。resolveErrorView 方法是由 resolveErrorView 提供的,通过 ErrorMvcAutoConfiguration 类的源码可以看到 Spring Boot 默认采用的 ErrorViewResolver 是 DefaultErrorViewResolver 。 ErrorMvcAutoConfiguration 的部分源码如下:

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

从源码中看出,如果用户没有定义 ErrorViewResolver ,那么默认使用的 ErrorViewResolver 是 DefaultErrorViewResolver ,正式 DefaultErrorViewResolver 中配置了默认去 error 目录下寻找 4xx.html、5xx.html,因此开发者想要自定义 Error 视图,只需要提供自己的 ErrorViewResolver 即可,代码如下:

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

代码解释:

  • 自定义 MyErrorViewResolver 实现 ErrorViewResolver 接口并实现接口中的 resolveErrorView 方法,使用 @Component 注解将该类注册到 Spring 容器中
  • 在 resolveErrorView 方法中,最后一个Map参数就是Spring Boot 提供的默认的5条Error信息(也可进行修改)。在 resolveErrorView 方法中,返回一个 ModelAndView ,在 ModelAndView 中设置 Error 视图和 Error 数据。
  • 理论上,开发者可以通过实现 ErrorViewResolver 来实现 Error 数据的自定义,但是如果只是单纯地想自定义 Error 数据,还是建议继承 DefaultErrorAttributes

接下来在 resources/templates 目录下提供 errorPage.html,如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>4xx</title>
</head>
<body>
<h3>errorPage</h3>
<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>

此时,无论请求发生4xx错误还是5xx错误,都会来到errorPage.html 页面
在这里插入图片描述

在这里插入图片描述

3. 完全自定义

前面提到的两种自定义方式都是对 BasicErrorController 类中的某个环节进行修补。查看 Error 自动化配置类 ErrorMvcAutoConfiguration 可以发现 BasicErrorController 本身只是一个默认的配置,相关源码如下:

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

从源码中可看出,若开发者没有提供自己的 ErrorController ,则Spring Boot 提供 BasicErrorController 作为默认的 ErrorController ,因此开发者需要更灵活的对 Error 视图和数据进行处理,只需要提供自己的 ErrorController 即可。
提供自己的 ErrorController 有两种方式:一种是实现 ErrorController 接口,另一种是直接继承 BasicErrorController 。由于 ErrorController 接口只提供一个待实现的方法,而 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);
        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);
    }

}

代码解释:

  • 自定义 MyErrorController 继承自 BasicErrorController 并添加 @Controller 注解,将 MyErrorController 注册到 Spring MVC 容器中
  • 由于 BasicErrorController 没有无参构造方法,因此在创建 BasicErrorController 实例时需要传递参数,在 MyErrorController 的构造方法上添加 @Autowired 注解注入所需参数
  • 参考 BasicErrorController 中的实现,重写errorHtml 和 error 方法,对 Error 的视图和数据进行充分的自定义

最后在 resources/templates 目录下提供 myErrorPage.html 页面作为视图页面,如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>4xx</title>
</head>
<body>
<h3>myErrorPage</h3>
<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>

此时访问如下:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小熊猫呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值