Springboot异常处理和自定义错误页面
1.异常来源
要处理程序发生的异常,首先需要知道异常来自哪里?
1.前端错误的的请求路径,会使得程序发生4xx错误,最常见的就是404,Springboot默认当发生这种错误的请求路径,pc端响应的页面如下

如果是移动端(手机端)将会响应json格式的数据,如下

2.Springboot异常处理原理
为什么我们请求错误的路径,boot会给我们返回一个上面错误页面或者json格式数据呢?原理是怎样的呢?
Springboot项目启动之后,执行有@SpringBootApplication注解的启动类的main方法,通过@EnableAutoConfiguration加载
springbootAutoConfiguration.jar包下的META-INF/spring.factories中的所有配置类(这些配置类加载之后,会将每个配置类里面的组件注入容器然后使用)其中一个自动配置

ErrorMvcAutoConfiguration,通过代码可以看到用到了以下四个组件
DefaultErrorAttributes、BasicErrorController、errorPageCustomizer、DefaultErrorViewResolver

其他三个基本类似,当出现4xx或者5xx等错误时,errorPageCustomizer就会生效,this.properties.getError().getPath())并来到/error请求,核心代码
//errorPageCustomizer
@Value("${error.path:/error}")
private String path = "/error";
而这个/error请求再由BasicErrorController处理,BasicErrorController是一个Controller,其中里面有两种处理方法,一种是HTML形式,
一种是JSON格式。其中访问者的信息可以从getErrorAttributes从获取。DefaultErrorAttributes是ErrorAttributes的实现类。
关键代码
@RequestMapping(produces = "text/html") //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 ? new ModelAndView("error", model) : modelAndView);
}
@RequestMapping
@ResponseBody //JSON
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);
}
当为HTML模式时,就会构建一个resolveErrorView类,而resolverErrorView继续调用ErrorViewResolver。关键代码
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
在我们没有做自定义配置时,ErrorViewResolver就会指向DefaultErrorViewResolver。
static {
//可以用4xx,5xx文件名来统一匹配错误,但是会以精确优先的原则
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);
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), 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);
//如果模版引擎可用就让模版引擎进行解析如:Template/error/404
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
//如果模版引擎不可用,就在静态资源文件夹下找资源文件,error/404
return resolveResource(errorViewName, model);
}
3.如何定制错误、异常响应
明白了boot处理异常机制,我们如何自定义异常响应规则呢?
第一种:pc端返回静态错误页面,手机端返回boot默认的json数据
如果项目中有模板引擎(jsp,thmeleaf,freemarker)的情况下,可以将错误页面命名为状态码.html放在模板引擎文件夹下的error文件夹下,
发生异常,不管是前端请求还是后端程序错误会来到对应的错误页面。可以将错误页面命名为4xx和5xx匹配所有的错误,
但是优先返回精确状态码.html页面;并且在模板引擎页面可以获取如下相关信息

这里模版引擎使用thmeleaf
4xx代码
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="'状态码:'+${status}"></title>
</head>
<body>
< img src="../images/404.jpg" style="width: 40%;">
<h1 th:text="'时间:'+${timestamp}"></h1>
<h1 th:text="'错误提示:'+${error}"></h1>
<h1 th:text="'异常对象:'+${exception}"></h1>
<h1 th:text="'异常信息:'+${message}"></h1>
</body>
</html>
5xx代码
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="'状态码:'+${status}"></title>
</head>
<body>
<h2>500</h2>
<h1 th:text="'时间:'+${timestamp}"></h1>
<h1 th:text="'错误提示:'+${error}"></h1>
<h1 th:text="'异常对象:'+${exception}"></h1>
<h1 th:text="'异常信息:'+${message}"></h1>
</body>
</html>
我们请求一个错误的地址路径

我们在程序代码中人为制造一个异常,请求响应

上面是有模版引擎的情况下处理错误以及异常的方式,
如果项目中没有模板引擎,(模板引擎找不到这个错误页面),静态资源文件夹static下找对应的4xx或者5xx或者更精确的错误页面。但是如果不用模板引擎,页面不能获取上面说的页面信息;
上面两种方式使用手机访问返回都是boot默认的json数据
第二种:pc端返回动态的页面 ,手机端返回动态json数据
上面第一种可以轻松的的处理异常,只需在指定的路径下放静态页面(无模版引擎的情况)或者携带相关信息的页面(有模版引擎),
缺点就是不能在页面携带我们想要展示的数据,比如当我们程序某处放生异常,我们要返回我们自己提示的错误信息。这种异常如果处理呢?
默认情况下,在 Spring Boot 中,所有的异常数据其实就是第一种所展示出来的 5几条数据,这些数据定义在 org.springframework.boot.web.reactive.error.DefaultErrorAttributes 类中,具体定义在 getErrorAttributes 方法中 :核心代码如下
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
Throwable error = getError(request);
HttpStatus errorStatus = determineHttpStatus(error);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", determineMessage(error));
handleException(errorAttributes, determineException(error), includeStackTrace);
return errorAttributes;
}
DefaultErrorAttributes 类本身则是在 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 异常自动配置类中定义的,
如果开发者没有自己提供一个 ErrorAttributes 的实例的话,那么 Spring Boot 将自动提供一个 ErrorAttributes 的实例,也就是 DefaultErrorAttributes 。
基于此 ,开发者自定义 ErrorAttributes 有两种方式 实现自定义数据:
1.直接实现 ErrorAttributes 接口
2.继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异常数据的处理已经完成,开发者可以直接使用。
package com.javayihao.top.config;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
if ((Integer)map.get("status") == 500) {
//这里根据自己需求设置
map.put("message", "服务器内部错误!");
}
if ((Integer)map.get("status") == 404) {
map.put("message", "路径不存在!");
}
return map;
}
}
我们服务器访问 错误路径
客户端响应
访问有异常的的控制器
客户端响应
当然上面我可以在程序任意位置抛出异常,使用全局异常处理器处理
自定义异常
全局异常处理器
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(MyException.class)
public String jsonErrorHandler(HttpServletRequest request, Exception e) {
Map<String, Object> map = new HashMap<>();
request.setAttribute("java.servlet.error.status_code", 500);
map.put("code", -1);
map.put("msg", e.getMessage());
request.setAttribute("ext", map);
//转发到error
return "forward:/error";
}
}
自定义ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
//返回的map就是页面或者json能获取的所有字段
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
//可以额外添加内容
map.put("company", "javayihao");
//取出异常处理器中的携带的数据
Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);//传入0代表从request中获取
map.put("ext", ext);
return map;
}
}
第三种:pc端返回动态json数据,手机端也返回动态json数据
这种方式主要是针对前后端分离的项目,我们自定义一个异常,在程序中用于抛出

定义一个返回结果对象(也可以不用定义,直接使用map)存储异常信息
定义一个全局异常处理器
/*ControllerAdvice用来配置需要进行异常处理的包和注解类型,
比如@ControllerAdvice(annotations = RestController.class)
只有类标有rescontrolle才会被拦截
*/
@ControllerAdvice
public class MyExceptionHandler {
//自己创建的异常按照自己编写的信息返回即可
@ExceptionHandler(value = MyException.class)
@ResponseBody
public ErrorInfo<String> errorInfo(HttpServletRequest req, MyException e) {
ErrorInfo<String> r = new ErrorInfo<>();
r.setCode(ErrorInfo.ERROR);
r.setMessage(e.getMessage());
r.setData("测试数据");
r.setUrl(req.getRequestURL().toString());
return r;
}
//系统异常时返回的异常编号为 -1 ,返回的异常信息为 “系统正在维护”;不能将原始的异常信息返回
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ErrorInfo<String> errorInfo(HttpServletRequest req, Exception e) {
ErrorInfo<String> r = new ErrorInfo<>();
r.setCode(ErrorInfo.ERROR);
r.setMessage("系统维护中");
return r;
}
}
Springboot异常处理和自定义错误页面的更多相关文章
- SpringBoot自定义错误页面,SpringBoot 404、500错误提示页面
SpringBoot自定义错误页面,SpringBoot 404.500错误提示页面 SpringBoot 4xx.html.5xx.html错误提示页面 ====================== ...
- springboot自定义错误页面
springboot自定义错误页面 1.加入配置: @Bean public EmbeddedServletContainerCustomizer containerCustomizer() { re ...
- Springboot - 自定义错误页面
Springboot 没找到页面或内部错误时,会访问默认错误页面.这节我们来自定义错误页面. 自定义错误页面 1.在resources 目录下面再建一个 resources 文件夹,里面建一个 err ...
- MVC4 自定义错误页面(转)
一.概述 MVC4框架自带了定义错误页,该页面位于Shared/Error,该页面能够显示系统未能捕获的异常,如何才能使用该页面: 二.使用步骤: 1.配置WebConfig文件,在System.We ...
- MVC4 自定义错误页面(三)
一.概述 MVC4框架自带了定义错误页,该页面位于Shared/Error,该页面能够显示系统未能捕获的异常,如何才能使用该页面: 二.使用步骤: 1.配置WebConfig文件,在System.We ...
- .net自定义错误页面实现升级篇
问题描述: 在上一篇博文 ".net自定义错误页面实现" 中已经介绍了在.net中如何实现自定义错误页面实现(有需要者可以去上一篇博文了解),单纯按照上一篇博文那样设置,能够实现所 ...
- Springboot学习05-自定义错误页面完整分析
Springboot学习06-自定义错误页面完整分析 前言 接着上一篇博客,继续分析Springboot错误页面问题 正文 1-自定义浏览器错误页面(只要将自己的错误页面放在指定的路径下即可) 1-1 ...
- Springboot学习04-默认错误页面加载机制源码分析
Springboot学习04-默认错误页面加载机制源码分析 前沿 希望通过本文的学习,对错误页面的加载机制有这更神的理解 正文 1-Springboot错误页面展示 2-Springboot默认错误处 ...
- MVC自定义错误页面
MVC异常处理主要有三种方案:1.基于HandleErrorAttribute重写OnException方法:2.基于Global.apsx添加Application_Error方法:3.直接在Web ...
随机推荐
- Django聚合查询 orm字段及属性
目录 一 聚合查询 1. 级联 级联删除 级联更新 2. 聚合函数 使用 aggregate 使用场景 3. 分组查询 语法 使用 annotate 代码 4. F与Q查询 F查询 Q查询 二 ORM ...
- 2016/10/21 java中的参数传方式
参考:http://blog.sina.com.cn/s/blog_59ca2c2a0100qhjx.html http://www.cnblogs.com/caiyao/p/4964176.html
- 剑指offer题解(Java版)
剑指offer题解(Java版) 从尾到头打印链表 题目描述 输入一个链表,按从尾到头的顺序返回一个ArrayList. 方法1:用一个栈保存从头到尾访问链表的每个结点的值,然后按出栈顺序将各个值存入 ...
- 生命周期感知 Lifecycle
奉上翻译原文地址: 处理生命周期 :翻译过程中加上了自己的一点理解.理解不对的地方直接评论就好. 生命周期感知组件可以感知其他组件的生命周期,例如 Activity,Fragment等,以便于在组件的 ...
- Appium(八):Appium API(二) 元素等待、元素操作
1. 元素等待 我们在使用脚本的时候,可能会由于网络.服务器处理.电脑等原因,我们想要找的元素没有加载出来,这个时候如果直接定位就可能会报错. 这个时候我们就可以设置元素等待了. 什么叫元素等待呢? ...
- webpack动态加载打包chunk命名
最近,遇到复杂h5页面开发,为了优化H5首屏加载速度,想到使用按需加载的方式,减少首次加载的JavaScript文件体积,于是将处理过程在这里记录一下,涉及到的主要是以下三点: 使用Webpack如何 ...
- Css里的box-shadow的值分别代表什么
以下为例: box-shadow:1px 2px 3px 4px color inset; 1px:表示沿x轴的正方向的长度(如果是负数,则为沿x轴的负方向的长度); 2px:表示沿y轴的正方向的长度 ...
- ruby读写文件的方法
读文件 #Ruby 读取文件 #方法一 file = File.open("/Users/Desktop/demo.txt","r") while line = ...
- Python骚操作!一行命令把电脑变成服务器!
不知道你有没有遇到这么一种情况,就是你有时候想要把电脑上的一些东西传输到你的手机或者 Pad ,你要么需要使用数据线连接到电脑,有时候还要装各种驱动才可以进行数据传输,要么需要借助第三方的工具,在局域 ...
- angular cli + primeNG
目录: 1.安装 angular cli 2.创建项目 3.构建路由 4.新建组件 5.组件之间的通信 6.引入primeNG 7.修改primeNG组件样式 8.问题 -------------- ...