前言

相信大家在刚开始体验 Springboot 的时候一定会经常碰到这个页面,也就是访问一个不存在的页面的默认返回页面。

如果是其他客户端请求,如接口测试工具,会默认返回JSON数据。

{
"timestamp":"2019-01-06 22:26:16",
"status":404,
"error":"Not Found",
"message":"No message available",
"path":"/asdad"
}

很明显,SpringBoot 根据 HTTP 的请求头信息进行了不同的响应处理。HTTP 相关知识可以参考此处

1. SpringBoot 异常处理机制

追随 SpringBoot 源码可以分析出默认的错误处理机制。

// org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
// 绑定一些错误信息 记为 1
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(
this.serverProperties.getError().isIncludeException());
}
// 默认处理 /error 记为 2
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
// 错误处理页面 记为3
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}
@Configuration
static class DefaultErrorViewResolverConfiguration { private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
}
// 决定去哪个错误页面 记为4
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext,
this.resourceProperties);
} }

结合上面的注释,上面代码里的四个方法就是 Springboot 实现默认返回错误页面主要部分。

1.1. errorAttributes

errorAttributes直译为错误属性,这个方法确实如此,直接追踪源代码。

代码位于:

// org.springframework.boot.web.servlet.error.DefaultErrorAttributes

这个类里为错误情况共享很多错误信息,如。

errorAttributes.put("timestamp", new Date());
errorAttributes.put("status", status);
errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
errorAttributes.put("errors", result.getAllErrors());
errorAttributes.put("exception", error.getClass().getName());
errorAttributes.put("message", error.getMessage());
errorAttributes.put("trace", stackTrace.toString());
errorAttributes.put("path", path);

这些信息用作共享信息返回,所以当我们使用模版引擎时,也可以像取出其他参数一样轻松取出。

1.2. basicErrorControll

直接追踪 BasicErrorController 的源码内容可以发现下面的一段代码。

// org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
@Controller
// 定义请求路径,如果没有error.path路径,则路径为/error
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController { // 如果支持的格式 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());
// 解析错误视图信息,也就是下面1.4中的逻辑
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
// 返回视图,如果没有存在的页面模版,则使用默认错误视图模版
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
} @RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
// 如果是接受所有格式的HTTP请求
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
// 响应HttpEntity
return new ResponseEntity<>(body, status);
}
}

由上可知,basicErrorControll 用于创建用于请求返回的 controller类,并根据HTTP请求可接受的格式不同返回对应的信息,所以在使用浏览器和接口测试工具测试时返回结果存在差异。

1.3. ererrorPageCustomizer

直接查看方法里的new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);

//org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer
/**
* {@link WebServerFactoryCustomizer} that configures the server's error pages.
*/
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;
}
// 注册错误页面
// this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
//getPath()得到如下地址,如果没有自定义error.path属性,则去/error位置
//@Value("${error.path:/error}")
//private String path = "/error";
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath
.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
} @Override
public int getOrder() {
return 0;
} }

由上可知,当遇到错误时,如果没有自定义 error.path 属性,则请求转发至 /error.

1.4. conventionErrorViewResolver

根据上面的代码,一步步深入查看 SpringBoot 的默认错误处理实现,查看看 conventionErrorViewResolver方法。下面是 DefaultErrorViewResolver 类的部分代码,注释解析。

// org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver

// 初始化参数,key 是HTTP状态码第一位。
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);
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
// 使用HTTP完整状态码检查是否有页面可以匹配
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
// 使用 HTTP 状态码第一位匹配初始化中的参数创建视图对象
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
} private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 拼接错误视图路径 /eroor/[viewname]
String errorViewName = "error/" + viewName;
// 使用模版引擎尝试创建视图对象
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
// 没有模版引擎,使用静态资源文件夹解析视图
return resolveResource(errorViewName, model);
} 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;
}

而 Thymeleaf 对于错误页面的解析实现。

//org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider
public class ThymeleafTemplateAvailabilityProvider
implements TemplateAvailabilityProvider {
@Override
public boolean isTemplateAvailable(String view, Environment environment,
ClassLoader classLoader, ResourceLoader resourceLoader) {
if (ClassUtils.isPresent("org.thymeleaf.spring5.SpringTemplateEngine",
classLoader)) {
String prefix = environment.getProperty("spring.thymeleaf.prefix",
ThymeleafProperties.DEFAULT_PREFIX);
String suffix = environment.getProperty("spring.thymeleaf.suffix",
ThymeleafProperties.DEFAULT_SUFFIX);
return resourceLoader.getResource(prefix + view + suffix).exists();
}
return false;
}
}

从而我们可以得知,错误页面首先会检查模版引擎文件夹下的 /error/HTTP状态码 文件,如果不存在,则检查去模版引擎下的/error/4xx或者 /error/5xx 文件,如果还不存在,则检查静态资源文件夹下对应的上述文件。

2. 自定义异常页面

经过上面的 SpringBoot 错误机制源码分析,知道当遇到错误情况时候,SpringBoot 会首先返回到模版引擎文件夹下的 /error/HTTP状态码 文件,如果不存在,则检查去模版引擎下的/error/4xx或者 /error/5xx 文件,如果还不存在,则检查静态资源文件夹下对应的上述文件。并且在返回时会共享一些错误信息,这些错误信息可以在模版引擎中直接使用。

errorAttributes.put("timestamp", new Date());
errorAttributes.put("status", status);
errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
errorAttributes.put("errors", result.getAllErrors());
errorAttributes.put("exception", error.getClass().getName());
errorAttributes.put("message", error.getMessage());
errorAttributes.put("trace", stackTrace.toString());
errorAttributes.put("path", path);

因此,需要自定义错误页面,只需要在模版文件夹下的 error 文件夹下防止4xx 或者 5xx 文件即可。

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[[${status}]]</title>
<!-- Bootstrap core CSS -->
<link href="/webjars/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body >
<div class="m-5" >
<p>错误码:[[${status}]]</p>
<p >信息:[[${message}]]</p>
<p >时间:[[${#dates.format(timestamp,'yyyy-MM-dd hh:mm:ss ')}]]</p>
<p >请求路径:[[${path}]]</p>
</div> </body>
</html>

随意访问不存在路径得到。

3. 自定义错误JSON

根据上面的 SpringBoot 错误处理原理分析,得知最终返回的 JSON 信息是从一个 map 对象中转换出来的,那么,只要能自定义 map 中的值,就可以自定义错误信息的 json 格式了。直接重写 DefaultErrorAttributes类的 getErrorAttributes 方法即可。

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest; import java.util.HashMap;
import java.util.Map; /**
* <p>
* 自定义错误信息JSON值
*
* @Author niujinpeng
* @Date 2019/1/7 15:21
*/
@Component
public class ErrorAttributesCustom extends DefaultErrorAttributes { @Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
String code = map.get("status").toString();
String message = map.get("error").toString();
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("code", code);
hashMap.put("message", message);
return hashMap;
}
}

使用 postman 请求测试。

4. 统一异常处理

使用 @ControllerAdvice 结合@ExceptionHandler 注解可以实现统一的异常处理,@ExceptionHandler 注解的类会自动应用在每一个被 @RequestMapping 注解的方法。当程序中出现异常时会层层上抛

import lombok.extern.slf4j.Slf4j;
import net.codingme.boot.domain.Response;
import net.codingme.boot.enums.ResponseEnum;
import net.codingme.boot.utils.ResponseUtill;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; /**
* <p>
* 统一的异常处理
*
* @Author niujinpeng
* @Date 2019/1/7 14:26
*/ @Slf4j
@ControllerAdvice
public class ExceptionHandle { @ResponseBody
@ExceptionHandler(Exception.class)
public Response handleException(Exception e) {
log.info("异常 {}", e);
if (e instanceof BaseException) {
BaseException exception = (BaseException) e;
String code = exception.getCode();
String message = exception.getMessage();
return ResponseUtill.error(code, message);
}
return ResponseUtill.error(ResponseEnum.UNKNOW_ERROR);
}
}

请求异常页面得到响应如下。

{
"code": "-1",
"data": [],
"message": "未知错误"
}

文章代码已经上传到 GitHub Spring Boot Web开发 - 错误机制

<完>

欢迎点赞关注!

本文原发于个人博客:https://www.codingme.net 转载请注明出处

Springboot 系列(七)Spring Boot web 开发之异常错误处理机制剖析的更多相关文章

  1. Springboot 系列(五)Spring Boot web 开发之静态资源和模版引擎

    前言 Spring Boot 天生的适合 web 应用开发,它可以快速的嵌入 Tomcat, Jetty 或 Netty 用于包含一个 HTTP 服务器.且开发十分简单,只需要引入 web 开发所需的 ...

  2. Springboot 系列(六)Spring Boot web 开发之拦截器和三大组件

    1. 拦截器 Springboot 中的 Interceptor 拦截器也就是 mvc 中的拦截器,只是省去了 xml 配置部分.并没有本质的不同,都是通过实现 HandlerInterceptor ...

  3. spring boot系列(二)spring boot web开发

    json 接口开发 在以前的spring 开发的时候需要我们提供json接口的时候需要做如下配置: 1 添加jackjson等jar包 2 配置spring controller扫描 3 对接的方法添 ...

  4. SpringBoot系列:Spring Boot使用模板引擎FreeMarker

    一.Java模板引擎 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档. 在jav ...

  5. SpringBoot系列:Spring Boot使用模板引擎Thymeleaf

    一.Java模板引擎 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档. 在jav ...

  6. SpringBoot系列:Spring Boot使用模板引擎JSP

    一.Java模板引擎 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档. 在jav ...

  7. Spring Boot Web 开发注解篇

    本文提纲 1. spring-boot-starter-web 依赖概述 1.1 spring-boot-starter-web 职责 1.2 spring-boot-starter-web 依赖关系 ...

  8. 四、Spring Boot Web开发

    四.Web开发 1.简介 使用SpringBoot: 1).创建SpringBoot应用,选中我们需要的模块: 2).SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可 ...

  9. SpringBoot系列:Spring Boot集成Spring Cache,使用RedisCache

    前面的章节,讲解了Spring Boot集成Spring Cache,Spring Cache已经完成了多种Cache的实现,包括EhCache.RedisCache.ConcurrentMapCac ...

随机推荐

  1. BZOJ_3144_[Hnoi2013]切糕_最小割

    BZOJ_3144_[Hnoi2013]切糕_最小割 Description Input 第一行是三个正整数P,Q,R,表示切糕的长P. 宽Q.高R.第二行有一个非负整数D,表示光滑性要求.接下来是R ...

  2. Mtcnn进行人脸剪裁和对齐

    from scipy import misc import tensorflow as tf import detect_face import cv2 import matplotlib.pyplo ...

  3. ToB蓝海的台阶-PaaS,SaaS技术详解

    前言 随着大量SaaS公司进入市场,我们看到颠覆性的软件服务以各种方式进入企业流程-从营销工具到支付系统.随着SaaS帮助优化业务流程,实现更流畅和自动化的运营,风险投资公司首先潜入池中寻找最优秀和最 ...

  4. 微信小程序中placeholder的样式

    通常,现代浏览器大多支持::placeholder选择器,用于设置placeholder的样式,但是在微信小程序中并不支持这种方式,而是提供了一个专门的属性(placeholder-class)来处理 ...

  5. shiro的SecurityUtis

    接着上一篇来继续分析shiro源码 这篇主要讲解shiro里面的SecurityUtils 首先我们看该类供我们在业务中用的仅有两个get方法,那么这两个get方法获取的subject和sercuri ...

  6. 基于tcp的套接字编程

    一,基础版服务器端客户端(一收一发,只有一个客户端链接) 服务器端: #Author : Kelvin #Date : 2019/1/28 22:10 from socket import * ser ...

  7. 基于Webpack, KnockoutJs,esyui,koeasyui实现类vue-cli生成的模板框架

    前后端分离的开发机制,基本上是开发现代业务系统的标配.可在国内某些特殊领域还是存在大量的以JQuery走天涯的现象,但其中也不泛有追求技术者,如不才的鄙人.不才的本人曾以JQuery走天涯:后又接受了 ...

  8. GC参考手册 —— GC 调优(命令篇)

    运用jvm自带的命令可以方便的在生产监控和打印堆栈的日志信息帮忙我们来定位问题!虽然jvm调优成熟的工具已经有很多:jconsole.大名鼎鼎的VisualVM,IBM的Memory Analyzer ...

  9. vs中开发web站点使IIS Express支持局域网连接

    vs中开发web站点使IIS Express支持局域网连接 在开发webapi的时候,客户端设备都会使用局域网的地址访问webapi,有时候需要调试api.这个时候就需要使用一些技巧了,这里我记录了我 ...

  10. Java集合详解7:HashSet,TreeSet与LinkedHashSet

    今天我们来探索一下HashSet,TreeSet与LinkedHashSet的基本原理与源码实现,由于这三个set都是基于之前文章的三个map进行实现的,所以推荐大家先看一下前面有关map的文章,结合 ...