SpringBoot第十四篇:统一异常处理
作者:追梦1819
原文:https://www.cnblogs.com/yanfei1819/p/10984081.html
版权声明:本文为博主原创文章,转载请附上博文链接!
引言
本文将谈论 SpringBoot 的默认错误处理机制,以及如何自定义错误响应。
版本信息
- JDK:1.8
- SpringBoot :2.1.4.RELEASE
- maven:3.3.9
- Thymelaf:2.1.4.RELEASE
- IDEA:2019.1.1
默认错误响应
我们新建一个项目,先来看看 SpringBoot 的默认响应式什么:
首先,引入 maven 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
然后,写一个请求接口:
package com.yanfei1819.customizeerrordemo.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Created by 追梦1819 on 2019-05-09.
*/
@Controller
public class DefaultErrorController {
@GetMapping("/defaultViewError")
public void defaultViewError(){
System.out.println("默认页面异常");
}
@ResponseBody
@GetMapping("/defaultDataError")
public void defaultDataError(){
System.out.println("默认的客户端异常");
}
}
随意访问一个8080端口的地址,例如 http://localhost:8080/a ,如下效果:
浏览器访问,返回一个默认页面

其它的客户端访问,返回确定的json字符串

以上是SpringBoot 默认的错误响应页面和返回值。不过,在实际项目中,这种响应对用户来说并不友好。通常都是开发者自定义异常页面和返回值,使其看起来更加友好、更加舒适。
默认的错误处理机制
在定制错误页面和错误响应数据之前,我们先来看看 SpringBoot 的错误处理机制。
ErrorMvcAutoConfiguration :

容器中有以下组件:
1、DefaultErrorAttributes
2、BasicErrorController
3、ErrorPageCustomizer
4、DefaultErrorViewResolver
系统出现 4xx 或者 5xx 错误时,ErrorPageCustomizer 就会生效:
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
private final DispatcherServletPath dispatcherServletPath;
protected ErrorPageCustomizer(ServerProperties properties,
DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
// 注册错误页面响应规则
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath
.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
@Override
public int getOrder() {
return 0;
}
}
上面的注册错误页面响应规则能够的到错误页面的路径(getPath):
@Value("${error.path:/error}")
private String path = "/error"; //(web.xml注册的错误页面规则)
public String getPath() {
return this.path;
}
此时会被 BasicErrorController 处理:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
}
BasicErrorController 中有两个请求:
// //产生html类型的数据;浏览器发送的请求来到这个方法处理
// MediaType.TEXT_HTML_VALUE ==> "text/html"
@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, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪个页面作为错误页面;包含页面地址和页面内容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
//产生json数据,其他客户端来到这个方法处理;
@RequestMapping
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);
}
上面源码中有两个请求,分别是处理浏览器发送的请求和其它浏览器发送的请求的。是通过请求头来区分的:
1、浏览器请求头

2、其他客户端请求头

resolveErrorView,获取所有的异常视图解析器 ;
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//获取所有的 ErrorViewResolver 得到 ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
DefaultErrorViewResolver,默认错误视图解析器,去哪个页面是由其解析得到的;
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 视图名,拼接在 error/ 后面
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
// 使用模板引擎的情况
return new ModelAndView(errorViewName, model);
}
// 未使用模板引擎的情况
return resolveResource(errorViewName, model);
}
其中 SERIES_VIEWS 是:
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 resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
以上代码可以总结为:
模板引擎不可用
就在静态资源文件夹下
找errorViewName对应的页面 error/4xx.html
如果,静态资源文件夹下存在,返回这个页面
如果,静态资源文件夹下不存在,返回null
定制错误响应
按照 SpringBoot 的默认异常响应,分为默认响应页面和默认响应信息。我们也分为定制错误页面和错误信息。
定制错误的页面
有模板引擎的情况
SpringBoot 默认定位到模板引擎文件夹下面的 error/ 文件夹下。根据发生的状态码的错误寻找到响应的页面。注意一点的是,页面可以"精确匹配"和"模糊匹配"。
精确匹配的意思是返回的状态码是什么,就找到对应的页面。例如,返回的状态码是 404,就匹配到 404.html. 模糊匹配,意思是可以使用 4xx 和 5xx 作为错误页面的文件名来匹配这种类型的所有错误。不过,"精确匹配"优先。
没有模板引擎
项目如果没有使用模板引擎,则在静态资源文件夹下面查找。
下面自定义异常页面,并模拟异常发生。
在以上的示例基础上,首先,自定义一个异常:
public class UserNotExistException extends RuntimeException {
public UserNotExistException() {
super("用户不存在");
}
}
然后,进行异常处理:
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
// 传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
// Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message","用户出错啦");
request.setAttribute("ext",map);
//转发到/error
return "forward:/error";
}
}
注意几点,一定要定制自定义的状态码,否则没有作用。
第三步,定制一个页面:
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>Internal Server Error | 服务器错误</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
<!--省略css代码-->
</style>
</head>
<body>
<h1>服务器错误</h1>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h1>status:[[${status}]]</h1>
<h2>timestamp:[[${timestamp}]]</h2>
<h2>exception:[[${exception}]]</h2>
<h2>message:[[${message}]]</h2>
<h2>ext:[[${ext.code}]]</h2>
<h2>ext:[[${ext.message}]]</h2>
</main>
</body>
</html>
最后,模拟一个异常:
@Controller
public class CustomizeErrorController {
@GetMapping("/customizeViewError")
public void customizeViewError(){
System.out.println("自定义页面异常");
throw new UserNotExistException();
}
}
启动项目,可以观察到以下结果:

定制响应的json
针对浏览器意外的其他客户端错误响应,相似的道理,我们先进行自定义异常处理:
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
然后模拟异常的出现:
@ResponseBody
@GetMapping("/customizeDataError")
public void customizeDataError(){
System.out.println("自定义客户端异常");
throw new UserNotExistException();
}
启动项目,看到结果是:

总结
异常处理同日志一样,也属于项目的“基础设施”,它的存在,可以扩大系统的容错处理,加强系统的健壮性。在自定义的基础上,优化了错误提示,对用户更加友好。
由于篇幅所限,以上的 SpringBoot 的内部错误处理机制也只属于“蜻蜓点水”。后期将重点分析 SpringBoot 的工作机制。
最后,如果需要完整代码,请移步至我的GitHub。
源码:我的GitHub

SpringBoot第十四篇:统一异常处理的更多相关文章
- SpringBoot第二十四篇:应用监控之Admin
作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/11457867.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言 前一章(S ...
- 跟我学SpringCloud | 第十四篇:Spring Cloud Gateway高级应用
SpringCloud系列教程 | 第十四篇:Spring Cloud Gateway高级应用 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 ...
- SpringBoot第十五篇:swagger构建优雅文档
作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/11007470.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言 前面的十四 ...
- Spring Cloud第十四篇 | Api网关Zuul
本文是Spring Cloud专栏的第十四篇文章,了解前十三篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring C ...
- 解剖SQLSERVER 第十四篇 Vardecimals 存储格式揭秘(译)
解剖SQLSERVER 第十四篇 Vardecimals 存储格式揭秘(译) http://improve.dk/how-are-vardecimals-stored/ 在这篇文章,我将深入研究 ...
- 第十四篇 Integration Services:项目转换
本篇文章是Integration Services系列的第十四篇,详细内容请参考原文. 简介在前一篇,我们查看了SSIS变量,变量配置和表达式管理动态值.在这一篇,我们使用SQL Server数据商业 ...
- Python之路【第十四篇】:AngularJS --暂无内容-待更新
Python之路[第十四篇]:AngularJS --暂无内容-待更新
- 【译】第十四篇 Integration Services:项目转换
本篇文章是Integration Services系列的第十四篇,详细内容请参考原文. 简介在前一篇,我们查看了SSIS变量,变量配置和表达式管理动态值.在这一篇,我们使用SQL Server数据商业 ...
- Egret入门学习日记 --- 第十四篇(书中 5.4~5.6节 内容)
第十四篇(书中 5.4~5.6节 内容) 书中内容: 总结 5.4节 内容重点: 1.如何编写自定义组件? 跟着做: 重点1:如何编写自定义组件? 文中提到了重要的两点. 好,我们来试试看. 第一步, ...
随机推荐
- C# 学习笔记 多态(一)虚方法
在面对对象编程中,类的三大特性分别为封装,继承,多态.其中多态的具体实现,依赖于三个方法,也就是虚方法,抽象类和接口. 多态的具体作用是什么呢?或者说多态的存在有什么意义呢?多态的存在有效的降低了程序 ...
- SQLServer 跨服务器链接 Access数据库
最近做了一个链接Access的实例,记录一笔. 如果你的Access数据库文件和SQLServer数据库在同一服务器上,可直接在数据库手动创建数据库链接 步骤如下: 打开新建链接,给你的链接起一个顺眼 ...
- 使用Debug Diagnostic Tool排除内存泄漏故障
在我之前的博文中(SQL Server内存泄漏),我解释了如何使用“!heap”命令识别哪个模块泄漏了内存.有时我们使用“!d”命令来找到模型或者使用搜索内存命令(s)不能通过显示内存找到原因. 在这 ...
- uni-app学习(六)好用的插件4
1. uni-app学习(六)好用的插件4 1.1. QQ音乐模板 点击这里 示例 1.2. 画廊(ynGallery)组件 点击这里 看起来不错的 示例 1.3. 评价模板 学到个动画用法 imag ...
- [20190505]关于latch 一些统计信息.txt
[20190505]关于latch 一些统计信息.txt --//我在两篇文章,提到一些latch的统计信息.链接如下:http://blog.itpub.net/267265/viewspace-2 ...
- 使用python实现后台系统的JWT认证
介绍 JWT协议似乎已经应用十分广泛,JSON Web Token--一种基于token的json格式web认证方法.基本的原理是,第一次认证通过用户名密码,服务端签发一个json格式的token.后 ...
- java中方法的重载和覆盖分别要满足的条件
1.重载:遵循“两同三不同” 两同:同一个类中的同名方法 三不同:形参的类型,个数,顺序不同 特别提醒: 返回值不同构不能方法重载 形参名称不同构不成方法重载 2.覆盖(重写)的要求 子类方法的名称, ...
- liteos错误处理(十一)
1. 概述 1.1 基本概念 错误处理指用户代码发生错误时,系统调用错误处理模块的接口函数,完成上报错误信息,并调用用户自己的钩子函数,进行特定的处理. 错误处理模块实现OS内部错误码记录功能.OS内 ...
- pdfium 保存pdf
// // Created by svenj on 2019/2/3. // extern "C" { #include <unistd.h> #include < ...
- kubectl-trace 基于bpftrace 的kubernetes 集群性能分析工具
kubectl-trace 是一个kubectl 的插件,我们可以使用基于bpftrace 的编程能力,来分析系统的性能问题, 强大,灵活,后边安装试用下 参考架构 参考资料 https://gith ...