SpingBoot错误信息处理及原理
SpringBoot错误信息处理机制
在一个web项目中,总需要对一些错误进行界面或者json数据返回,已实现更好的用户体验,SpringBoot中提供了对于错误处理的自动配置
ErrorMvcAutoConfiguration
这个类存放了所有关于错误信息的自动配置。
1. SpringBoot处理错误请求的流程
访问步骤:
- 首先客户端访问了错误界面。例:404或者500
SpringBoot
注册错误请求/error
。通过ErrorPageCustomizer
组件实现- 通过
BasicErrorController
处理/error
,对错误信息进行了自适应处理,浏览器会响应一个界面,其他端会响应一个json
数据 - 如果响应一个界面,通过
DefaultErrorViewResolver
类来进行具体的解析。可以通过模板引擎解析也可以解析静态资源文件,如果两者都不存在则直接返回默认的错误JSON
或者错误View
- 通过
DefaultErrorAttributes
来添加具体的错误信息
源代码
//错误信息的自动配置
public class ErrorMvcAutoConfiguration {
//响应具体的错误信息
@Bean
public DefaultErrorAttributes errorAttributes() {
return
}
//处理错误请求
@Bean
public BasicErrorController basicErrorController() {
return
}
//注册错误界面
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
return
}
//注册错误界面,错误界面的路径为/error
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
//服务器基本配置
private final ServerProperties properties;
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
//获取服务器配置中的错误路径/error
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
//注册错误界面
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
//this.properties.getError()
public class ServerProperties{
//错误信息的配置文件
private final ErrorProperties error = new ErrorProperties();
}
//getPath
public class ErrorProperties {
@Value("${error.path:/error}")
private String path = "/error";
//处理/error请求,从配置文件中取出请求的路径
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
//浏览器行为,通过请求头来判断,浏览器返回一个视图
@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);
}
//其他客户端行为处理,返回一个JSON数据
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
//添加错误信息
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
2. 响应一个视图
步骤:
- 客户端出现错误
SpringBoot
创建错误请求/error
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,调用父类AbstractErrorController的方法
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
public abstract class AbstractErrorController{
private final List<ErrorViewResolver> errorViewResolvers;
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
Iterator var5 = this.errorViewResolvers.iterator();
//遍历所有的错误视图解析器
ModelAndView modelAndView;
do {
if (!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
//调用视图解析器的方法,
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null);
return modelAndView;
}
}
public interface ErrorViewResolver {
ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
}
- 处理具体的视图跳转
//处理视图跳转
public DefaultErrorViewResolver{
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//将状态码作为视图名称传入解析
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
//视图名称为error文件夹下的400.html等状态码文件
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
//是否存在模板引擎进行解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//存在则返回解析以后的数据,不存在调用resolveResource方法进行解析
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
//如果静态资源文件中存在,返回静态文件下的,如果不存在返回SpringBoot默认的
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
String[] var3 = this.resourceProperties.getStaticLocations();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception var8) {
}
}
return null;
}
应用:
- 在模板引擎文件下创建error文件夹,里面放置各种状态码的视图文件,模板引擎会解析
- 在静态资源下常见error文件夹,里面放置各种状态码的视图文件,模板引擎不会解析
- 如果没有状态码文件,则返回springBoot默认界面视图
3.响应一个json数据
在BasicErrorController
处理/error
请求的时候不适用浏览器默认请求
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
//调用父类的方法获取所有的错误属性
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
父类方法:
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
//调用ErrorAttributes接口的getErrorAttributes方法,
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
添加错误信息
public class DefaultErrorAttributes{
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
返回的json数据有:
- status
- error
- exception
- message
- trace
- path
可以通过模板引擎获取这些值
4.自定义异常返回自定义的异常数据
4.1@ControllerAdvice注解
SpringMVC提供的注解,可以用来定义全局异常,全局数据绑定,全局数据预处理
@ControllerAdivice
定义全局的异常处理
- 通过
@ExceptionHandler(XXXException.class)
执行该方法需要处理什么异常,然后返回什么数据或者视图
//json数据返回 ,处理自定义用户不存在异常
@ResponseBody
@ExceptionHandler(UserException.class)
public Map<String,String> userExceptionMethod(UserException us){
Map<String,String> map = new HashMap<>();
map.put("message",us.getMessage());
return map ;
}
@ControllerAdvice
定义全局数据
- 通过
@ModelAttribute(Name="key")
定义全局数据的key - 默认方法的返回值的名称作为键
- 在
Controller
中通过Model
获取对应的key的值
@ControllerAdvice
public MyConfig{
@ModelAttribute(name = "key")
public Map<String,String> defineAttr(){
Map<String,String> map = new HashMap<>();
map.put("message","幻听");
map.put("update","许嵩");
return map ;
}
@Controller
public UserController{
@GetMapping("/hello")
public Map<String, Object> hello(Model model){
Map<String, Object> asMap = model.asMap();
System.out.println(asMap);
//{key={message='上山',update='左手一式太极拳'}}
return asMap ;
}
}
@ControllerAdvice
处理预处理数据(当需要添加的实体,属性名字相同的时候)
- 在
Controller
的参数中添加ModelAttribute
作为属性赋值的前缀 - 在
ControllerAdvice
修饰的类中,结合InitBinder
来绑定对应的属性(该属性为ModelAttribite的value值 - 在
@InitBinder
修饰的方法中通过WebDataBinder
添加默认的前缀
@Getter@Setter
public class Book {
private String name ;
private int age ;
@Getter@Setter
public class Music {
private String name ;
private String author ;
//这种方式的处理,spring无法判断Name属性给哪个bean赋值,所以需要通过别名的方式来进行赋值
@PostMapping("book")
public String book(Book book , Music music){
System.out.println(book);
System.out.println(music);
return "404" ;
}
//使用以下的方式
@PostMapping("/book")
public String book(@ModelAttribute("b")Book book , @ModelAttribute("m")Music music){
System.out.println(book);
System.out.println(music);
return "404" ;
}
public MyCOnfiguration{
@InitBinder("b")
public void b(WebDataBinder webDataBinder){
webDataBinder.setFieldDefaultPrefix("b.");
}
@InitBinder("m")
public void m(WebDataBinder webDataBinder){
webDataBinder.setFieldDefaultPrefix("m.");
}
}
4.2自定义异常JSON
浏览器和其他客户端都只能获取json
数据
@ControllerAdvice
public class MyExceptionHandler {
//处理UserException异常
@ResponseBody
@ExceptionHandler(UserException.class)
public Map<String,String> userExceptionMethod(UserException us){
Map<String,String> map = new HashMap<>();
map.put("message",us.getMessage());
map.put("status","500");
return map ;
}
4.2自定义异常返回一个视图,拥有自适应效果
@ExceptionHandler(UserException.class)
public String allException(UserException e,HttpServletRequest request){
Map<String,String> map = new HashMap<>();
map.put("message",e.getMessage());
map.put("load","下山");
request.setAttribute("myMessage",map);
//设置状态码,SpringBoot通过java.servlet.error.status_code来设置状态码
request.setAttribute("javax.servlet.error.status_code",400);
return "forward:/error" ;
}
当抛出UserException
异常的时候,来到这个异常处理器,给这个请求中添加了数据,再转发到这个error请求中,交给ErrorPageCustomizer
处理,由于设置了请求状态码400
则返回的视图为400或4XX视图,或者直接返回一个JSON
数据
{
"timestamp": "2020-02-19T04:17:43.394+0000",
"status": 400,
"error": "Bad Request",
"message": "用户名不存在异常",
"path": "/crud/user/login"
}
- 不足:JSON数据中没有显示我们自己定义的错误信息
4.3自定义错误信息
前面提到SpringBoot对错误信息的定义存在于DefaultErrorAttributes类的getErrorAttributes中,我们可以直接继承这个类,或者实现ErrorAttributes接口,然后将我们自己实现的错误处理器添加到容器中即可。
继承DefaultErrorAttributes
和实现ErrorAttributes
接口的区别是,继承以后仍然可以使用SpringBoot默认的错误信息,我们仅仅对该错误信息进行了增强;实现了ErrorAttributes
接口,完全自定义错误信息
- 实现
ErrorAttributes
接口
public class MyErrorHandler implements ErrorAttributes {
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("status",500);
errorAttributes.put("message","用户不存在异常");
return errorAttributes;
}
@Override
public Throwable getError(WebRequest webRequest) {
return null;
}
- 继承
DefaultErrorAttributes
的方法
public class MyErrorHandler extends DefaultErrorAttributes {
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//调用父类方法,直接在默认错误信息的基础上添加
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
errorAttributes.put("timestamp", new Date());
errorAttributes.put("message","用户不存在异常");
return errorAttributes;
}
}
- 将定义的错误信息器添加到容器中
- 通过
@Component
组件直接将MyErrorHandler
组件添加到容器中 - 通过
@Bean
在配置类中将组件添加到容器中
- 通过
@Bean
public DefaultErrorAttributes getErrorHandler(){
return new MyErrorHandler();
}
- 下面解决上一节中没有出现我们自定义的异常信息
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
errorAttributes.put("timestamp", new Date());
errorAttributes.put("message","用户不存在异常");
//指定从哪个作用域中取值
webRequest.getAttribute("myMessage", RequestAttributes.SCOPE_REQUEST);
return errorAttributes;
将在异常处理器中定义的错误信息取出,然后添加到错误信息中。
SpingBoot错误信息处理及原理的更多相关文章
- ubuontu16.04安装Opencv库引发的find_package()错误信息处理及其简单使用
在安装完Opencv库之后,打算测试一下Opencv库是否成功安装.下面是用的例子对应的.cpp代码以及对应的CMakeLists.txt代码: .cpp文件: #include <stdio. ...
- ThinkPHP错误信息处理
index.php入口文件中打开APP_DEBUG// 开启调试模式define('APP_DEBUG', TRUE); // 开启Trace信息 'SHOW_PAGE_TRACE' =>tru ...
- PHP开发环境正确的错误信息处理
正确记录配置 php.ini display_errors = On error_reporting = E_ALL log_errors = On error_log = F:/data/php/e ...
- [译]Javascript中的错误信息处理(Error handling)
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- centos mysql错误信息处理
mysql_secure_installation 提示错误:Enter current password for root (enter for none):ERROR 1045 (28000): ...
- 详解EBS接口开发之供应商导入(补充)--错误信息处理
check reject details on records of AP_SUPPLIER_INT SELECT s.parent_table,s.reject_lookup_code,S.LAST ...
- HTTP 错误 500.23 - Internal Server Error 检测到在集成的托管管道模式下不适用的 ASP.NET 设置。
检测到在集成的托管管道模式下不适用的ASP.NET设置的解决方法(非简单设置为[经典]模式). - CatcherX 2014-03-11 11:03 27628人阅读 评论(2) 收藏 举报 分类 ...
- php部分---文件上传:错误处理、 客户端和服务器端的限制
1.客户端页面 <!---客户端的配置 1.表单页面 2.表单发送方式为post 3.表单form中添加enctype="multipart/form-data" ----- ...
- WDCP LNMPA和LNMP 504 Gateway time-out错误的解决方法
Nginx的特点是处理静态很给力,Apache的特点是处理动态很稳定,两者结合起来便是LNMPA,nginx处理前端,apache处理后端,这样处理静态会很快,处理动态会很稳定.当我以为安装完成以后便 ...
随机推荐
- C# 阿里云查询、删除文件
class Program { static string regionId = "cn-shanghai"; static string accessKeyId = " ...
- 【转】最受欢迎的8位Java牛人
本文由 ImportNew - 唐尤华 翻译自 javatyro.如需转载本文,请先参见文章末尾处的转载要求. 下面是8位Java牛人,他们为Java社区编写框架.产品.工具或撰写书籍改变了Java编 ...
- 快速回顾MySQL:简单查询操作
利用空闲时间花几分钟回顾一下 7.1 检索数据 为了查询出数据库表中的行(数据),使用SELECE语句. 格式: # 第一种 SELECT * FROM <table_name>; # 第 ...
- c++快读与快输模板
快读 inline int read() { ; ; char ch=getchar(); ; ch=getchar();} )+(X<<)+ch-'; ch=getchar();} if ...
- 【LC_Lesson3】---回文数的判别
判断一个整数是否是回文数.回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数. 示例 1: 输入: 121 输出: true 示例 2: 输入: -121 输出: false 解释: 从左向 ...
- 【C_Language】---一份程序看懂C程序printf()的几种常用用法
闲来继续巩固我的学习之路,今天略微整理了一下,C程序中Printf()的一些常用用法,虽然自己以前好像会,但是不够系统,今天大致整理了一些,先贴上来看看,以后在看到其他,继续补充,希望能帮到一些像我一 ...
- 计算机基础——常用的Dos命令
d:——切换指定硬盘分区 cd——切换目录,例如:cd D:\下载(使用此命令之前先使用 d: 命令) netstat -ano——查看所有端口号 netstat -a——查看开启了哪些端口 nets ...
- Excel查找匹配函数的16种方法
作者:高顿初级会计链接:https://zhuanlan.zhihu.com/p/79795779来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 1.普通查找 查找李晓 ...
- Ubuntu18.04 安装配置mongodb
一.安装 # 1. 更新 sudo apt-get update # 2. 安装 sudo apt-get install -y mongodb # 3. 查看是否安装成功 # a. 服务状态 sud ...
- Java电商支付系统实战(一)- 简介
现如今,支付成为热点 对于电商业务,这都是不可或缺的 核心功能剖析 下单->支付 nginx 将用户请求反向代理到我们编写的电商系统 = 下单 之后,点击支付跳转到支付系统,最后对接 通过跳转将 ...