Spring Boot 自定义错误页

自定义错误页

在处理异常时,开发者可以根据实际情况返回不同的页面,但是这种异常处理方式一般用来处理应用级别的异常,有一些容器级别的错误就处理不了,例如Filter中抛出异常,使用@ControllerAdvice定义的全局异常处理机制就无法处理。因此,Spring Boot中对于异常的处理还有另外的方式,这就是本节要介绍的内容。
在Spring Boot 中,默认情况下,如果用户在发起请求时发生了404错误,Spring Boot 会有一个默认的页面展示给用户,如图所示。

在这里插入图片描述

如果发起请求时发生了500错误,Spring Boot也会有一个默认的页面展示给用户,如图所示。

在这里插入图片描述

事实上, Spring Boot在返回错误信息时不一定返回HTML页面,而是根据实际情况返回HTML页面或者一段JSON(若开发者发起 Ajax 请求,则错误信息是一段JSON)。对于开发者而言,这一段HTML或者JSON都能够自由定制。

Spring Boot 中的错误默认是由BasicErrorController类来处理的,该类中的核心方法主要有两个:

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

	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		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的视图也找不到,用户就会看到本节一开始展示的两个错误提示页面。整个错误处理流程大致就是这样的。

1. 简单配置

通过上面的介绍,读者可能已经发现,要自定义错误页面其实很简单,提供4xx和5xx页面即可。如果开发者不需要向用户展示详细的错误信息,那么可以把错误信息定义成静态页面,直接在resources/static目录下创建error目录,然后在error目录中创建错误展示页面。
错误展示页面的命名规则有两种:一种是4xx.html、5xx.html;另一种是直接使用响应码命名文件,例如404.html、405.html、500.html。第二种命名方式划分得更细,当出错时,不同的错误会展示不同的错误页面,如图所示。

在这里插入图片描述

此时,当用户访问一个不存在的路径时,就会展示404.html页面中的内容

在这里插入图片描述

这种定义都是使用了静态HTML页面,无法向用户展示完整的错误信息,若采用视图模板技术,则可以向用户展示更多的错误信息。如果要使用HTML模板,那么先引入模板相关的依赖,这里以Thymeleaf为例,Thymeleaf页面模板默认处于classpath:/templates/目录下
因此在该目录下

在这里插入图片描述

先创建error目录,再创建错误展示页,如图所示。

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

Spring Boot 在这里一共返回了 条错误相关的信息,分别是 timestamp, status,error,message 以及 path。 5xx.html 页面的内容与 4xx.html 页面的内容一致。 此时,用户访问 个不存在的地址, 4xx.html 页面中的内容将被展示出来,如图所示。

在这里插入图片描述

若用户访问一个会抛异常的地址,例如上文的/hello接口,则会展示5xx.html页面的内容,如图所示。

在这里插入图片描述

注意:
若用户定义了多个错误页面,则响应码.html页面的优先级高于4xx.html、5xx.html页面的优先级,即若当前是一个404错误,则优先展示404.html而不是4xx.html;动态页面的优先级高于静态页面,即若resources/templates和resources/static 下同时定义了4xx.html,则优先展示resources/templates/4xx.html。

2. 复杂配置

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

2.1 自定义Error数据

自定义 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();
}

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

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes=super.getErrorAttributes(webRequest,options);
        errorAttributes.put("custommsg","出错了");
        errorAttributes.remove("error");
        return errorAttributes;
    }
}

代码解释:

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

此时,当系统抛出异常时,错误信息将被修改,以动态页面模板404.html为例,修改404.html,代码如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org/">
<head>
    <meta charset="UTF-8">
    <title>Title</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>
    <tr>
        <td>custommsg</td>
        <td th:text="${custommsg}"></td>
    </tr>
</table>
</body>
</html>

在第29~30行添加了custommsg属性,此时访问一个不存在的路径,就能看到自定义的Error信息,并且可以看到默认的error被移除了,如图所示。

在这里插入图片描述

2.2 自定义Error视图

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

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

从这一段源码可以看到,如果用户没有定义 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信息(可以按照前面自定义Error数据的步骤对这5条消息进行修改)。在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>Title</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>
    <tr>
        <td>custommsg</td>
        <td th:text="${custommsg}"></td>
    </tr>
</table>
</body>
</html>

在errorPage.html中,除了展示Spring Boot提供的5条Error信息外,也展示了开发者自定义的Error信息。此时,无论请求发生4xx 的错误还是发生5xx的错误,都会来到errorPage.html页面。

在这里插入图片描述

2.3 完全自定义

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

public class ErrorMvcAutoConfiguration {
  ...

	@Bean
	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
			ObjectProvider<ErrorViewResolver> errorViewResolvers) {
		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
				errorViewResolvers.orderedStream().collect(Collectors.toList()));
	}
    
	...
}

从这段源码中可以看到,若开发者没有提供自己的 ErrorController,则 Spring Boot 提供BasicErrorController作为默认的ErrorController。因此,如果开发者需要更加灵活地对Error视图和数据进行处理,那么只需要提供自己的ErrorController即可。提供自己的 ErrorController有两种方式:一种是实现 ErrorController接口,另一种是直接继承BasicErrorController。由于ErrorController接口只提供一个待实现的方法,而BasicErrorController已经实现了很多功能,因此这里选择第二种方式,即通过继承BasicErrorController来实现自己的ErrorController。具体定义如下:

@Controller
public class MyBasicErrorController extends BasicErrorController {

    @Autowired
    public MyBasicErrorController(ErrorAttributes errorAttributes,
                             ServerProperties serverProperties,
                             List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, serverProperties.getError(), errorViewResolvers);
    }

    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        // 获取 Spring Boot 默认提供的错误信息,然后添加一个自定义的错误信息
        Map<String, Object> model = Collections
                .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        ModelAndView modelAndView = new ModelAndView("myerrorPage", model, status);
        return modelAndView;
    }

    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        // 获取 Spring Boot 默认提供的错误信息,然后添加一个自定义的错误信息
        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
        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>Title</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>

访问一个不存在的页面,就可以看到自定义的错误提示了。

Spring Boot中对异常的处理还是非常容易的,Spring Boot虽然提供了非常丰富的自动化配置方案,但是也允许开发者根据实际情况进行完全的自定义,开发者在使用过程中可以结合具体情况选择合适的 Error 处理方案。

访问一个不存在的页面,就可以看到自定义的错误提示了。

Spring Boot中对异常的处理还是非常容易的,Spring Boot虽然提供了非常丰富的自动化配置方案,但是也允许开发者根据实际情况进行完全的自定义,开发者在使用过程中可以结合具体情况选择合适的 Error 处理方案。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值