Spring Boot 错误页面解析原理(超级无敌详细)
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);
有模板引擎的情况下,首先会在
templates/error/
文件下获取状态码.html文件
如果
状态码.html文件
不存在的情况下,templates/error/
文件夹下获取 状态码对应的 **“4xx.html”或"5xx.html"**文件。如果都不存在的话会在一下静态资源文件夹 获取资源, 精确优先(优先寻找精确的状态码.html),否则查早对应的 **“4xx.html”或"5xx.html"**文件
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/"};
如果以上都不能获取,则返回默认的 Whitelabel Error Page
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
Spring Boot 错误页面解析原理(超级无敌详细)相关推荐
- Spring/Spring Boot错误页面处理的多种方式(404 not found等)
Spring/Spring Boot错误页面处理的多种方式(404 not found等) 简单来说, tomcat容器会根据error-page的内容forward到指定location(会再走一次 ...
- Spring Boot html页面解析之jsoup
目的 我们要对一个页面进行数据抓取,并导出doc文档 html解析器 jsoup 直接解析某个URL地址.HTML文本内容.它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操 ...
- SpringBoot 自动配置原理(超级无敌详细)-2
SpringBoot 自动配置原理(超级无敌详细)-1 2.自动配置的实现 刚刚我们整体的过了一下主配置文件是如何实现的,但我们还没深入的研究如何实现自动装配功能.我们回到这个文件下,找一个具体的自动 ...
- Spring Boot的自动化配置原理
转载自 Spring Boot的自动化配置原理 随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得格外笨重.繁多的配置.低下的开发效率.复杂的部署流程以及第三方技术集成难度大等问 ...
- spring boot 源码解析23-actuate使用及EndPoint解析
前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...
- Spring Boot注解的运行原理
Spring Boot 是一个基于 Spring Framework 的开源框架,通过简化配置和开发过程,使 Spring 应用程序的开发变得更加快速和便捷.在 Spring Boot 中,注解是非常 ...
- Spring Boot错误–创建在类路径资源DataSourceAutoConfiguration中定义的名称为“ dataSource”的bean时出错...
大家好,如果您使用的是Spring Boot,并且遇到诸如"无法为数据库类型NONE确定嵌入式数据库驱动程序类"或"在类路径资源ataSourceAutoConfigur ...
- Spring自定义命名空间的解析原理与实现
Spring自定义命名空间的解析原理与实现 原理 由上篇文章refresh() -> obtainFreshBeanFactory()跟踪源码可知Spring在解析除默认命名空间import.a ...
- Spring Boot从入门到精通(超详细)
Spring Boot从入门到精通(超详细) _kayden_ 2020-07-20 15:19:22 9491 正在上传-重新上传取消 收藏 184 分类专栏: springboot 文章标签: ...
- 【超级无敌详细的黑马前端笔记!即时更新~】
[超级无敌详细的黑马前端笔记!即时更新~] 这个笔记,是我自己在同步学习黑马前端使用的,不可以商用哦 学习路径 基础概念铺垫(了解) 认识网页 五大浏览器和渲染引擎 Web标准 HTML初体验 HTM ...
最新文章
- linux下的qt缺少iostream,c – iostream:没有这样的文件或目录
- JAVA SE学习day14:解析XML
- 16位汇编 call调用函数 通过栈来传递参数
- 集合元素处理(传统方式)
- abp框架java,【Net】ABP框架学习之正面硬钢
- Phonegap 环境配置
- underscore源码剖析之整体架构
- MySQL建表语句综合
- python图像边缘检测_Python进行图片水平边缘检测prewitt算子法
- 攻略!嵌入式开发需要学习哪些内容?
- 2022 区块链(GameFi)游戏行业研究报告
- Python 玩转数据 12 - 数据读写 Data I/O: Pandas 读写 JSON File Format
- 逆向CrackMe-01写注册机
- C++ 面向对象高级开发(侯捷)
- idea提示java.sql.SQLException: Access denied for user ‘‘@‘localhost‘ (using password: NO)
- python 获取当前运行的DCC工具
- vue本地读取图片转码Base64
- 在Hbulider中点击事件会出现两次
- 从小米上市了解CDR和“同股不同权”
- android mvvm官方demo,Android MVVM实战Demo完全解析
热门文章
- 1、ruby语法抄写练习
- 货币等额换算_换算单位和货币的最佳免费程序和网站
- FullCalendar日历控件vue使用记录
- CodeForces 949A Zebras
- array unshift php,php – 用于多维数组的array_unshift
- CocosCreator 项目可编译debug无法编译release问题特例
- android ip 黑白名单,“IP 黑白名单”功能说明
- linux亮度调节指令,Linux Mint 亮度调节——xrandr命令学习
- CISCO路由器、交换机设备破解密码
- sqli-labs注入特色分类教程1-15【手动注入与sqlmap全面双解】