5.SpringBoot 错误页面

1.默认效果

当我们在运行SpringBoot的时候,访问一个不存在的页面,SpringBoot默认为我们返回一个空白页面,如下所示

这个空白页面主要包括 默认错误路径、时间戳、错误提示消息 和错误状态码


但如果我们使用其他的客户端(非浏览器),例如 Postman工具 发送 http://127.0.0.1:8080/noPage 请求时,默认响应客户端的是JSON数据,如下图所示

2.原理

出现上面的两种默认效果的原因是 SpringBoot为我们自动配置了 错误处理自动配置的 控制器ErrorMvcAutoConfiguration

首先,我们ErrorMvcAutoConfiguration 为我们配置了这几个组件

  • DefaultErrorAttributes
  • BasicErrorController
  • ErrorPageCustomizer
  • DefaultErrorViewResolver
 @Bean@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)public DefaultErrorAttributes errorAttributes() {return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());}@Bean@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,ObjectProvider<ErrorViewResolver> errorViewResolvers) {return new BasicErrorController(errorAttributes, this.serverProperties.getError(),errorViewResolvers.orderedStream().collect(Collectors.toList()));}@Beanpublic ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);}@Configuration(proxyBeanMethods = false)static class DefaultErrorViewResolverConfiguration {private final ApplicationContext applicationContext;private final ResourceProperties resourceProperties;DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,ResourceProperties resourceProperties) {this.applicationContext = applicationContext;this.resourceProperties = resourceProperties;}@Bean@ConditionalOnBean(DispatcherServlet.class)@ConditionalOnMissingBean(ErrorViewResolver.class)DefaultErrorViewResolver conventionErrorViewResolver() {return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);}}

ErrorPageCustomizer

@Beanpublic ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);}

首先,它会实例化出一个 ErrorPageCustomizer对象,在ErrorPageCustomizer对象中有一个重要的方法registerErrorPages

 @Overridepublic void registerErrorPages(ErrorPageRegistry errorPageRegistry) {ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));errorPageRegistry.addErrorPages(errorPage);}

然后就会调用该方法,注册一个错误页面, 通过 getPath()获取到路径,getPath存在默认的值为"/error",如果在配置文件中配值了``server.error.path=/errors那就会从配置文件中取出error.path`的值

 @Value("${error.path:/error}")private String path = "/error";

  • 总结

    • ErrorPageCustomizer组件的作用就是 当 用户请求一旦发生错误时,就会获得 path (错误页面的路径),然后将我们转发到 path 路径下。 并将该路径信息发给BasicErrorController 进行处理

BasicErrorController

从名字上,我们可以知道它是一个 基本的错误控制器。 核心代码如下:

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {@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);}@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {HttpStatus status = getStatus(request);if (status == HttpStatus.NO_CONTENT) {return new ResponseEntity<>(status);}Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));return new ResponseEntity<>(body, status);}

它是用来处理 配置文件下的 server.error.path的请求,如果 server.error.path 未配置时,就会处理 配置文件下的 error.path的请求,如果也没配置,就会处理/error

在这个控制器组件下面 存在着两个 @RequestMapping ,都是处理同一个请求路径的 方法。只是这两个方法的返回值不一样

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) // 产生html类型的数据
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
 TEXT_HTML_VALUE = "text/html

但是竟然这两种方式都是用于处理一个请求的,为什么浏览器获取的数据和postMan获取的会不一样呢?

原因是浏览器发送的请求的请求头包含这串信息

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

也就是说 浏览器优先接收 text/html类型的数据。

而postMan发送请求时 的请求头如下,它没有指明 希望接收的数据类型,所以服务端给它返回一个JSON类型的数据


那重点来了,返回页面时,又是如何实现的呢?

 @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) // TEXT_HTML_VALUE = "text/htmlpublic 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);}

当用户发送请求,发送错误时

  • 首先,它会通过请求获取响应状态码和一些model数据,,然后返回一个 modelAndViews , 通过第7行,我们可以知道 modelAndViews 是通过调用 resolveErrorView() 方法获得的。modelAndViews 包含了需要返回的地址,页面等信息
  • 响应页面是调用了 resolveErrorView() ,如下
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;
}

第2行:遍历所有的 ErrorViewResolver ,调用resolver. resolveErrorView(request, status, model) 如果存在着 一个 modelAndView 就返回.

在之前,Spring Boot 为我们自动注册了一个 DefaultErrorViewResolver 到容器中,也就是说resolveErrorView() 方法将会由 DefaultErrorViewResolver 进行调用。

DefaultErrorViewResolver

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {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);}@Overridepublic 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) {String errorViewName = "error/" + viewName;TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext);if (provider != null) {return new ModelAndView(errorViewName, model);}return resolveResource(errorViewName, model);}

首先,我们可以发现 第5-10行 ,为map中添加了一下客户端错误,用"4xx"表示;服务端错误,用"5xx"表示。

然后就会调用 第13-19行的 resolveErrorView()方法,解析错误视图。

因为发生了错误,所以modelAndView 自然就是等于 null 了,那么就会调用第16行的resolve()方法进行解析,并返回一个modelAndView对象。

resolve(SERIES_VIEWS.get(status.series()), model)
private ModelAndView resolve(String viewName, Map<String, Object> model)

调用resolve()方法时,传入了两个参数,分别为视图名称和model数据

第23行:this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext)如果模板引擎 根据这个错误视图名称能找到对应的模板时,就用模板引擎进行解析

如果解析的结果不为空时,即解析成功过,就将模板引擎解析的 modelAndView的结果进行返回,否者就调用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;}

调用resolveResource方法的时候,从第2行,我们可以发现它会在静态资源下找一个资源名为 错误状态码.html的静态资源(如果我们的错误状态码为404,那么它就会 静态资源文件夹下找 404.html

3.错误页面总结:

1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;

2)我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);

  1. 有模板引擎的情况下,首先会在templates/error/文件下获取 状态码.html文件

  2. 如果状态码.html文件 不存在的情况下,templates/error/文件夹下获取 状态码对应的 **“4xx.html”或"5xx.html"**文件。

  3. 如果都不存在的话会在一下静态资源文件夹 获取资源, 精确优先(优先寻找精确的状态码.html),否则查早对应的 **“4xx.html”或"5xx.html"**文件

        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/"};
    
  4. 如果以上都不能获取,则返回默认的 Whitelabel Error Page

  5. 页面能获取的信息;

​ timestamp:时间戳

​ status:状态码

​ error:错误提示

​ exception:异常对象

​ message:异常消息

​ errors:JSR303数据校验的错误都在这里

Spring Boot 错误页面解析原理(超级无敌详细)相关推荐

  1. Spring/Spring Boot错误页面处理的多种方式(404 not found等)

    Spring/Spring Boot错误页面处理的多种方式(404 not found等) 简单来说, tomcat容器会根据error-page的内容forward到指定location(会再走一次 ...

  2. Spring Boot html页面解析之jsoup

    目的 我们要对一个页面进行数据抓取,并导出doc文档 html解析器 jsoup 直接解析某个URL地址.HTML文本内容.它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操 ...

  3. SpringBoot 自动配置原理(超级无敌详细)-2

    SpringBoot 自动配置原理(超级无敌详细)-1 2.自动配置的实现 刚刚我们整体的过了一下主配置文件是如何实现的,但我们还没深入的研究如何实现自动装配功能.我们回到这个文件下,找一个具体的自动 ...

  4. Spring Boot的自动化配置原理

    转载自 Spring Boot的自动化配置原理 随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得格外笨重.繁多的配置.低下的开发效率.复杂的部署流程以及第三方技术集成难度大等问 ...

  5. spring boot 源码解析23-actuate使用及EndPoint解析

    前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...

  6. Spring Boot注解的运行原理

    Spring Boot 是一个基于 Spring Framework 的开源框架,通过简化配置和开发过程,使 Spring 应用程序的开发变得更加快速和便捷.在 Spring Boot 中,注解是非常 ...

  7. Spring Boot错误–创建在类路径资源DataSourceAutoConfiguration中定义的名称为“ dataSource”的bean时出错...

    大家好,如果您使用的是Spring Boot,并且遇到诸如"无法为数据库类型NONE确定嵌入式数据库驱动程序类"或"在类路径资源ataSourceAutoConfigur ...

  8. Spring自定义命名空间的解析原理与实现

    Spring自定义命名空间的解析原理与实现 原理 由上篇文章refresh() -> obtainFreshBeanFactory()跟踪源码可知Spring在解析除默认命名空间import.a ...

  9. Spring Boot从入门到精通(超详细)

    Spring Boot从入门到精通(超详细) _kayden_ 2020-07-20 15:19:22 9491 正在上传-重新上传取消​ 收藏 184 分类专栏: springboot 文章标签:  ...

  10. 【超级无敌详细的黑马前端笔记!即时更新~】

    [超级无敌详细的黑马前端笔记!即时更新~] 这个笔记,是我自己在同步学习黑马前端使用的,不可以商用哦 学习路径 基础概念铺垫(了解) 认识网页 五大浏览器和渲染引擎 Web标准 HTML初体验 HTM ...

最新文章

  1. linux下的qt缺少iostream,c – iostream:没有这样的文件或目录
  2. JAVA SE学习day14:解析XML
  3. 16位汇编 call调用函数 通过栈来传递参数
  4. 集合元素处理(传统方式)
  5. abp框架java,【Net】ABP框架学习之正面硬钢
  6. Phonegap 环境配置
  7. underscore源码剖析之整体架构
  8. MySQL建表语句综合
  9. python图像边缘检测_Python进行图片水平边缘检测prewitt算子法
  10. 攻略!嵌入式开发需要学习哪些内容?
  11. 2022 区块链(GameFi)游戏行业研究报告
  12. Python 玩转数据 12 - 数据读写 Data I/O: Pandas 读写 JSON File Format
  13. 逆向CrackMe-01写注册机
  14. C++ 面向对象高级开发(侯捷)
  15. idea提示java.sql.SQLException: Access denied for user ‘‘@‘localhost‘ (using password: NO)
  16. python 获取当前运行的DCC工具
  17. vue本地读取图片转码Base64
  18. 在Hbulider中点击事件会出现两次
  19. 从小米上市了解CDR和“同股不同权”
  20. android mvvm官方demo,Android MVVM实战Demo完全解析

热门文章

  1. 1、ruby语法抄写练习
  2. 货币等额换算_换算单位和货币的最佳免费程序和网站
  3. FullCalendar日历控件vue使用记录
  4. CodeForces 949A Zebras
  5. array unshift php,php – 用于多维数组的array_unshift
  6. CocosCreator 项目可编译debug无法编译release问题特例
  7. android ip 黑白名单,“IP 黑白名单”功能说明
  8. linux亮度调节指令,Linux Mint 亮度调节——xrandr命令学习
  9. CISCO路由器、交换机设备破解密码
  10. sqli-labs注入特色分类教程1-15【手动注入与sqlmap全面双解】