前言

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

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

  1. {
  2. "timestamp":"2019-01-06 22:26:16",
  3. "status":404,
  4. "error":"Not Found",
  5. "message":"No message available",
  6. "path":"/asdad"
  7. }

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

1. SpringBoot 异常处理机制

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

  1. // org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
  2. // 绑定一些错误信息 记为 1
  3. @Bean
  4. @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
  5. public DefaultErrorAttributes errorAttributes() {
  6. return new DefaultErrorAttributes(
  7. this.serverProperties.getError().isIncludeException());
  8. }
  9. // 默认处理 /error 记为 2
  10. @Bean
  11. @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
  12. public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
  13. return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
  14. this.errorViewResolvers);
  15. }
  16. // 错误处理页面 记为3
  17. @Bean
  18. public ErrorPageCustomizer errorPageCustomizer() {
  19. return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
  20. }
  21. @Configuration
  22. static class DefaultErrorViewResolverConfiguration {
  23. private final ApplicationContext applicationContext;
  24. private final ResourceProperties resourceProperties;
  25. DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
  26. ResourceProperties resourceProperties) {
  27. this.applicationContext = applicationContext;
  28. this.resourceProperties = resourceProperties;
  29. }
  30. // 决定去哪个错误页面 记为4
  31. @Bean
  32. @ConditionalOnBean(DispatcherServlet.class)
  33. @ConditionalOnMissingBean
  34. public DefaultErrorViewResolver conventionErrorViewResolver() {
  35. return new DefaultErrorViewResolver(this.applicationContext,
  36. this.resourceProperties);
  37. }
  38. }

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

1.1. errorAttributes

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

代码位于:

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

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

  1. errorAttributes.put("timestamp", new Date());
  2. errorAttributes.put("status", status);
  3. errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
  4. errorAttributes.put("errors", result.getAllErrors());
  5. errorAttributes.put("exception", error.getClass().getName());
  6. errorAttributes.put("message", error.getMessage());
  7. errorAttributes.put("trace", stackTrace.toString());
  8. errorAttributes.put("path", path);

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

1.2. basicErrorControll

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

  1. // org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
  2. @Controller
  3. // 定义请求路径,如果没有error.path路径,则路径为/error
  4. @RequestMapping("${server.error.path:${error.path:/error}}")
  5. public class BasicErrorController extends AbstractErrorController {
  6. // 如果支持的格式 text/html
  7. @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  8. public ModelAndView errorHtml(HttpServletRequest request,
  9. HttpServletResponse response) {
  10. HttpStatus status = getStatus(request);
  11. // 获取要返回的值
  12. Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
  13. request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  14. response.setStatus(status.value());
  15. // 解析错误视图信息,也就是下面1.4中的逻辑
  16. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  17. // 返回视图,如果没有存在的页面模版,则使用默认错误视图模版
  18. return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
  19. }
  20. @RequestMapping
  21. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  22. // 如果是接受所有格式的HTTP请求
  23. Map<String, Object> body = getErrorAttributes(request,
  24. isIncludeStackTrace(request, MediaType.ALL));
  25. HttpStatus status = getStatus(request);
  26. // 响应HttpEntity
  27. return new ResponseEntity<>(body, status);
  28. }
  29. }

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

1.3. ererrorPageCustomizer

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

  1. //org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer
  2. /**
  3. * {@link WebServerFactoryCustomizer} that configures the server's error pages.
  4. */
  5. private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
  6. private final ServerProperties properties;
  7. private final DispatcherServletPath dispatcherServletPath;
  8. protected ErrorPageCustomizer(ServerProperties properties,
  9. DispatcherServletPath dispatcherServletPath) {
  10. this.properties = properties;
  11. this.dispatcherServletPath = dispatcherServletPath;
  12. }
  13. // 注册错误页面
  14. // this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())
  15. @Override
  16. public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
  17. //getPath()得到如下地址,如果没有自定义error.path属性,则去/error位置
  18. //@Value("${error.path:/error}")
  19. //private String path = "/error";
  20. ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath
  21. .getRelativePath(this.properties.getError().getPath()));
  22. errorPageRegistry.addErrorPages(errorPage);
  23. }
  24. @Override
  25. public int getOrder() {
  26. return 0;
  27. }
  28. }

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

1.4. conventionErrorViewResolver

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

  1. // org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver
  2. // 初始化参数,key 是HTTP状态码第一位。
  3. static {
  4. Map<Series, String> views = new EnumMap<>(Series.class);
  5. views.put(Series.CLIENT_ERROR, "4xx");
  6. views.put(Series.SERVER_ERROR, "5xx");
  7. SERIES_VIEWS = Collections.unmodifiableMap(views);
  8. }
  9. @Override
  10. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
  11. Map<String, Object> model) {
  12. // 使用HTTP完整状态码检查是否有页面可以匹配
  13. ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
  14. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  15. // 使用 HTTP 状态码第一位匹配初始化中的参数创建视图对象
  16. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  17. }
  18. return modelAndView;
  19. }
  20. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  21. // 拼接错误视图路径 /eroor/[viewname]
  22. String errorViewName = "error/" + viewName;
  23. // 使用模版引擎尝试创建视图对象
  24. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
  25. .getProvider(errorViewName, this.applicationContext);
  26. if (provider != null) {
  27. return new ModelAndView(errorViewName, model);
  28. }
  29. // 没有模版引擎,使用静态资源文件夹解析视图
  30. return resolveResource(errorViewName, model);
  31. }
  32. private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
  33. // 遍历静态资源文件夹,检查是否有存在视图
  34. for (String location : this.resourceProperties.getStaticLocations()) {
  35. try {
  36. Resource resource = this.applicationContext.getResource(location);
  37. resource = resource.createRelative(viewName + ".html");
  38. if (resource.exists()) {
  39. return new ModelAndView(new HtmlResourceView(resource), model);
  40. }
  41. }
  42. catch (Exception ex) {
  43. }
  44. }
  45. return null;
  46. }

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

  1. //org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider
  2. public class ThymeleafTemplateAvailabilityProvider
  3. implements TemplateAvailabilityProvider {
  4. @Override
  5. public boolean isTemplateAvailable(String view, Environment environment,
  6. ClassLoader classLoader, ResourceLoader resourceLoader) {
  7. if (ClassUtils.isPresent("org.thymeleaf.spring5.SpringTemplateEngine",
  8. classLoader)) {
  9. String prefix = environment.getProperty("spring.thymeleaf.prefix",
  10. ThymeleafProperties.DEFAULT_PREFIX);
  11. String suffix = environment.getProperty("spring.thymeleaf.suffix",
  12. ThymeleafProperties.DEFAULT_SUFFIX);
  13. return resourceLoader.getResource(prefix + view + suffix).exists();
  14. }
  15. return false;
  16. }
  17. }

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

2. 自定义异常页面

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

  1. errorAttributes.put("timestamp", new Date());
  2. errorAttributes.put("status", status);
  3. errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
  4. errorAttributes.put("errors", result.getAllErrors());
  5. errorAttributes.put("exception", error.getClass().getName());
  6. errorAttributes.put("message", error.getMessage());
  7. errorAttributes.put("trace", stackTrace.toString());
  8. errorAttributes.put("path", path);

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

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

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

3. 自定义错误JSON

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

  1. import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
  2. import org.springframework.stereotype.Component;
  3. import org.springframework.web.context.request.WebRequest;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. /**
  7. * <p>
  8. * 自定义错误信息JSON值
  9. *
  10. * @Author niujinpeng
  11. * @Date 2019/1/7 15:21
  12. */
  13. @Component
  14. public class ErrorAttributesCustom extends DefaultErrorAttributes {
  15. @Override
  16. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  17. Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
  18. String code = map.get("status").toString();
  19. String message = map.get("error").toString();
  20. HashMap<String, Object> hashMap = new HashMap<>();
  21. hashMap.put("code", code);
  22. hashMap.put("message", message);
  23. return hashMap;
  24. }
  25. }

使用 postman 请求测试。

4. 统一异常处理

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

  1. import lombok.extern.slf4j.Slf4j;
  2. import net.codingme.boot.domain.Response;
  3. import net.codingme.boot.enums.ResponseEnum;
  4. import net.codingme.boot.utils.ResponseUtill;
  5. import org.springframework.web.bind.annotation.ControllerAdvice;
  6. import org.springframework.web.bind.annotation.ExceptionHandler;
  7. import org.springframework.web.bind.annotation.ResponseBody;
  8. import javax.servlet.http.HttpServletRequest;
  9. /**
  10. * <p>
  11. * 统一的异常处理
  12. *
  13. * @Author niujinpeng
  14. * @Date 2019/1/7 14:26
  15. */
  16. @Slf4j
  17. @ControllerAdvice
  18. public class ExceptionHandle {
  19. @ResponseBody
  20. @ExceptionHandler(Exception.class)
  21. public Response handleException(Exception e) {
  22. log.info("异常 {}", e);
  23. if (e instanceof BaseException) {
  24. BaseException exception = (BaseException) e;
  25. String code = exception.getCode();
  26. String message = exception.getMessage();
  27. return ResponseUtill.error(code, message);
  28. }
  29. return ResponseUtill.error(ResponseEnum.UNKNOW_ERROR);
  30. }
  31. }

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

  1. {
  2. "code": "-1",
  3. "data": [],
  4. "message": "未知错误"
  5. }

文章代码已经上传到 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. Python基础语法 系统学习

    Python 中的基础语法最大的特点就是优雅和简洁.入门学习Python的难度相比较其他语言也比较小. 我个人比较推荐以下三个学习方式(根据个人情况和喜好,可选择任意一个): 1.  菜鸟在线:出品的 ...

  2. XSS SQL CSRF

    XSS(Cross Site Script,跨站脚本攻击)是向网页中注入恶意脚本在用户浏览网页时在用户浏览器中执行恶意脚本的攻击方式.跨站脚本攻击分有两种形式:反射型攻击(诱使用户点击一个嵌入恶意脚本 ...

  3. 关于WebSocket需要知道

    WebSocket 概念 WebSocket是再单个TCP连接上进行双工通讯的协议,仅需要通过一次握手两个之间就可以创建持久性的连接,进行双向数据传输.WebSocket 是HTML5新增加的协议. ...

  4. Python猫荐书系列之五:Python高性能编程

    稍微关心编程语言的使用趋势的人都知道,最近几年,国内最火的两种语言非 Python 与 Go 莫属,于是,隔三差五就会有人问:这两种语言谁更厉害/好找工作/高工资…… 对于编程语言的争论,就是猿界的生 ...

  5. Hibernate内容详解

    一:引入Hibernate的jar包 http://t.cn/EioD1xk 二:配置Hibernate的核心配置文件hibernate.cfg.xml <!DOCTYPE hibernate- ...

  6. vue 中使用promise

    init1(){return new Promise((resolve, reject) => { let data={ dateStr:this.time }; api.get('url', ...

  7. Prncnfg.vbs参数详解

    语法: Cscript Prncnfg {-g | -t | -x | -?} [-S <ServerName>] [-P <PrinterName>] [-z <New ...

  8. kubernetes实战之运行aspnetcore webapi微服务 - kubernetes

    1.预备工作 unbuntu 16.04 or above docker for linux kubernetes for linux 集群环境 2.使用vs2017创建一个web api应用程序,并 ...

  9. 【安富莱TCPnet网络教程】HTTP通信实例

    第41章      HTTP超文本传输协议基础知识 本章节为大家讲解HTTP(HyperText Transfer Protocol,超文本传输协议),从本章节开始,正式进入嵌入式Web的设计和学习. ...

  10. 吴恩达机器学习笔记58-协同过滤算法(Collaborative Filtering Algorithm)

    在之前的基于内容的推荐系统中,对于每一部电影,我们都掌握了可用的特征,使用这些特征训练出了每一个用户的参数.相反地,如果我们拥有用户的参数,我们可以学习得出电影的特征. 但是如果我们既没有用户的参数, ...