Springboot学习04-默认错误页面加载机制源码分析

前沿

    希望通过本文的学习,对错误页面的加载机制有这更神的理解

正文

1-Springboot错误页面展示

2-Springboot默认错误处理逻辑

1-将请求转发到BasicErrorController控制器来处理请求,

2-浏览器请求响应BasicErrorController的errorHtml()方法,APP等客户端响应error()方法

3-以浏览器的404错为例:最终返回一个modelAndView

3-1-调用BasicErrorController的errorHtml(HttpServletRequest request, HttpServletResponse response)方法,其中status=404;//详见源码L-134
3-2-调用AbstractErrorController的resolveErrorView方法,遍历ErrorMvcAutoConfiguration.errorViewResolvers,寻找需要的modelAndView;//详见源码L-142;162
3-3-ErrorMvcAutoConfiguration.errorViewResolvers会有一个默认的DefaultErrorViewResolver,于是便执行DefaultErrorViewResolver.resolveErrorView()方法;//详见源码L-171;190
3-4-DefaultErrorViewResolver.resolveErrorView()的具体实现:调用当前的this.resolve(status, model),创建modelAndView;//即寻找error/404页面 //详见源码L-191;199
3-5-如果创建error/404视图失败(即找不到error/404视图),则创建error/4XX视图;否则,继续创建视图;//详见源码L-192;193
3-6-如果创建error/4XX视图失败(即找不到error/4XX视图),则创建默认名为error的视图,而error视图在静态累WhitelabelErrorViewConfiguration中进行配置和加载(即Springboot默认的Whitelabel Error Page页面);//详见源码L-144
3-7-根据实际获取到的视图,进行渲染

3-源码分析 1//1-ErrorMvcAutoConfiguration配置类

  1. //1-ErrorMvcAutoConfiguration配置类
  2. package org.springframework.boot.autoconfigure.web.servlet.error;
  3. @Configuration
  4. @AutoConfigureBefore({WebMvcAutoConfiguration.class})//在WebMvcAutoConfiguration 配置之前完成peizhi
  5. public class ErrorMvcAutoConfiguration {
  6.  
  7. //注册了一个 专门收集 error 发生时错误信息的bean
  8. //DefaultErrorAttributes 实现了HandlerExceptionResolver, 通过对异常的处理, 填充 错误属性 ErrorAttributes 。 这个是boot 中的 controller 出现异常的时候会使用到的
  9. @Bean
  10. @ConditionalOnMissingBean(
  11. value = {ErrorAttributes.class},
  12. search = SearchStrategy.CURRENT
  13. )
  14. public DefaultErrorAttributes errorAttributes() {
  15. return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
  16. }
  17.  
  18. //注册 BasicErrorController。 BasicErrorController 完成对所有 controller 发生异常情况的处理, 包括 异常和 4xx, 5xx 子类的;处理默认/error请求
  19. @Bean
  20. @ConditionalOnMissingBean(
  21. value = {ErrorController.class},
  22. search = SearchStrategy.CURRENT
  23. )
  24. public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
  25. return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
  26. }
  27. //注册 错误页面的 定制器
  28. @Bean
  29. public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
  30. return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
  31. }
  32.  
  33. }
  34.  
  35. //1-1-ErrorMvcAutoConfigurationde配置类的内部类:WhitelabelErrorViewConfiguration
  36. @Configuration
  37. @ConditionalOnProperty(
  38. prefix = "server.error.whitelabel",
  39. name = {"enabled"},
  40. matchIfMissing = true
  41. )
  42. @Conditional({ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition.class})
  43. protected static class WhitelabelErrorViewConfiguration {
  44. //StaticView就是ErrorMvcAutoConfigurationde配置类的内部类:StaticView
  45. private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView();
  46.  
  47. protected WhitelabelErrorViewConfiguration() {
  48. }
  49.  
  50. @Bean(name = {"error"})
  51. @ConditionalOnMissingBean(name = {"error"})
  52. public View defaultErrorView() {
  53. return this.defaultErrorView;
  54. }
  55.  
  56. @Bean
  57. @ConditionalOnMissingBean
  58. public BeanNameViewResolver beanNameViewResolver() {
  59. BeanNameViewResolver resolver = new BeanNameViewResolver();
  60. resolver.setOrder(2147483637);
  61. return resolver;
  62. }
  63. }
  64.  
  65. //1-2-ErrorMvcAutoConfigurationde配置类的内部类:StaticView
  66. //WhitelabelErrorViewConfiguration 逻辑
  67. //1-WhitelabelErrorViewConfiguration 注册了 一个View, 同时 注册了BeanNameViewResolver,如果之前没有注册的话。
  68. //2-BeanNameViewResolver 也是可以对View 进行处理的, 它的处理方式是根据 view 的name 查找对应的bean。 这里 defaultErrorView 也是一个bean, 其名字是 error。 即:如果发现请求是 /error, 那么如果其他 ViewResolver 处理不了, 就BeanNameViewResolver 来处理,BeanNameViewResolver 把defaultErrorView 渲染到浏览器
  69. //3-可以看到, defaultErrorView 通常是异常处理的最后一个围墙, 因为 BeanNameViewResolver的优先级比较低defaultErrorView(实际就是StaticView)实现了 View , 主要就是完成了对 页面的渲染, 提供了一个 render 方法。
  70. private static class StaticView implements View {
  71. private static final Log logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);
  72.  
  73. private StaticView() {
  74. }
  75.  
  76. public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  77. if (response.isCommitted()) {
  78. String message = this.getMessage(model);
  79. logger.error(message);
  80. } else {
  81. StringBuilder builder = new StringBuilder();
  82. Date timestamp = (Date)model.get("timestamp");
  83. Object message = model.get("message");
  84. Object trace = model.get("trace");
  85. if (response.getContentType() == null) {
  86. response.setContentType(this.getContentType());
  87. }
  88.  
  89. builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>There was an unexpected error (type=").append(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
  90. if (message != null) {
  91. builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
  92. }
  93.  
  94. if (trace != null) {
  95. builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
  96. }
  97.  
  98. builder.append("</body></html>");
  99. response.getWriter().append(builder.toString());
  100. }
  101. }
  102. }
  103.  
  104. //1-3-ErrorMvcAutoConfigurationde配置类的内部类:ErrorPageCustomizer
  105. private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
  106. private final ServerProperties properties;
  107. private final DispatcherServletPath dispatcherServletPath;
  108.  
  109. protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
  110. this.properties = properties;
  111. this.dispatcherServletPath = dispatcherServletPath;
  112. }
  113. //把 /error 这样的errorpage 注册到了servlet容器,使得它异常的时候,会转发到/error
  114. public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
  115. ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
  116. errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
  117. }
  118.  
  119. public int getOrder() {
  120. return 0;
  121. }
  122. }
  123.  
  124. //2-1-BasicErrorController类
  125. package org.springframework.boot.autoconfigure.web.servlet.error;
  126. @Controller
  127. @RequestMapping({"${server.error.path:${error.path:/error}}"})
  128. public class BasicErrorController extends AbstractErrorController {
  129.  
  130. //当请求出现错误时,浏览器响应 ModelAndView errorHtml
  131. @RequestMapping(produces = {"text/html"})
  132. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  133. //示例:status = 404 NOT_FOUND
  134. HttpStatus status = this.getStatus(request);
  135. //这里的 model 是相关错误信息;示例:model={"timestamp":"Thu Dec 20 09:12:09 CST 2018","status" :"404","error": "Not Found","message": "No message available","path" :"/111"}
  136. Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  137. response.setStatus(status.value());
  138. //这个完成了具体的处理过程;获取视图
  139. //这里的resolveErrorView 是AbstractErrorController.AbstractErrorController
  140. ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
  141. //如果modelAndView=null;则返回 new ModelAndView("error", model);
  142. return modelAndView != null ? modelAndView : new ModelAndView("error", model);
  143. }
  144.  
  145. //这里相对上面的方法,简单很多,它不会去使用 viewResolver 去处理, 因为它不需要任何的 view ,而是直接返回 text 格式数据,而不是 html 格式数据
  146. @RequestMapping
  147. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  148. Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
  149. HttpStatus status = this.getStatus(request);
  150. return new ResponseEntity(body, status);
  151. }
  152.  
  153. }
  154.  
  155. //2-2-AbstractErrorController抽象类
  156. package org.springframework.boot.autoconfigure.web.servlet.error;
  157. public abstract class AbstractErrorController implements ErrorController {
  158.  
  159. protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
  160. //这个errorViewResolvers 就是ErrorMvcAutoConfiguration.errorViewResolvers 成员变量,errorViewResolvers包含DefaultErrorViewResolver
  161. Iterator var5 = this.errorViewResolvers.iterator();
  162. ModelAndView modelAndView;
  163. do {
  164. if (!var5.hasNext()) {
  165. return null;
  166. }
  167. ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
  168. modelAndView = resolver.resolveErrorView(request, status, model);
  169. } while(modelAndView == null);
  170.  
  171. return modelAndView;
  172. }
  173. }
  174.  
  175. //3-DefaultErrorViewResolver类
  176. package org.springframework.boot.autoconfigure.web.servlet.error;
  177. // DefaultErrorViewResolver 逻辑
  178. //1-DefaultErrorViewResolver 作为 ErrorViewResolver 注入到 ErrorMvcAutoConfiguration 的构造中去;而 errorViewResolvers 其实就是直接 交给了 BasicErrorController。 也就是说, BasicErrorController 处理错误的时候,会使用 DefaultErrorViewResolver 提供的内容来进行页面渲染。
  179. //2-DefaultErrorViewResolver是一个纯 boot 的内容,专门处理发生 error时候的 view
  180. //3-当请求需要一个error view, 就先去 error/ 目录下面去找 error/ + viewName + .html 的文件(这里的viewName通常是 404 ,500 之类的错误的response status code); 找到了 就直接展示(渲染)它。 否则就尝试去匹配4xx, 5xx,然后去找error/4xx.html或者 error/5xx.html两个页面,找到了就展示它。
  181. public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
  182. private static final Map<Series, String> SERIES_VIEWS;
  183. private ApplicationContext applicationContext;
  184. private final ResourceProperties resourceProperties;
  185.  
  186. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  187. ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
  188. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  189. modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
  190. }
  191.  
  192. return modelAndView;
  193. }
  194.  
  195. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  196. //示例:errorViewName = error/404
  197. String errorViewName = "error/" + viewName;
  198. //示例:从applicationContext中获取viewName="error/404"的可用模版
  199. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
  200. //如果provider=null,则返回this.resolveResource(errorViewName, model)
  201. //this.resolveResource<--见下面代码-->
  202. return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
  203. }
  204.  
  205. //获取静态资源视图
  206. private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
  207. // 静态的 location 包括 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
  208. String[] var3 = this.resourceProperties.getStaticLocations();
  209. int var4 = var3.length;
  210.  
  211. for(int var5 = 0; var5 < var4; ++var5) {
  212. String location = var3[var5];
  213.  
  214. try {
  215. Resource resource = this.applicationContext.getResource(location);
  216. resource = resource.createRelative(viewName + ".html");
  217. //资源必须要存在, 才会返回
  218. if (resource.exists()) {
  219. return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
  220. }
  221. } catch (Exception var8) {
  222. ;
  223. }
  224. }
  225. //如果各个静态目录下都没有找到那个html文件,那么就还是 返回null, 交给白标吧
  226. return null;
  227. }
  228.  
  229. static {
  230. Map<Series, String> views = new EnumMap(Series.class);
  231. views.put(Series.CLIENT_ERROR, "4xx");
  232. views.put(Series.SERVER_ERROR, "5xx");
  233. SERIES_VIEWS = Collections.unmodifiableMap(views);
  234. }
  235.  
  236. private static class HtmlResourceView implements View {
  237. private Resource resource;
  238.  
  239. HtmlResourceView(Resource resource) {
  240. this.resource = resource;
  241. }
  242.  
  243. public String getContentType() {
  244. return "text/html";
  245. }
  246.  
  247. public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  248. response.setContentType(this.getContentType());
  249. FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
  250. }
  251. }
  252. }

参考资料:

1-https://www.cnblogs.com/FlyAway2013/p/7944568.html

Springboot学习04-默认错误页面加载机制源码分析的更多相关文章

  1. Springboot 加载配置文件源码分析

    Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...

  2. ElasticSearch 启动时加载 Analyzer 源码分析

    ElasticSearch 启动时加载 Analyzer 源码分析 本文介绍 ElasticSearch启动时如何创建.加载Analyzer,主要的参考资料是Lucene中关于Analyzer官方文档 ...

  3. 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

    目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...

  4. springboot Properties加载顺序源码分析

    关于properties: 在spring框架中properties为Environment对象重要组成部分, springboot有如下几种种方式注入(优先级从高到低): 1.命令行 java -j ...

  5. jQuery实现DOM加载方法源码分析

    传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... }  但 ...

  6. Spring Cloud Nacos实现动态配置加载的源码分析

    理解了上述Environment的基本原理后,如何从远程服务器上加载配置到Spring的Environment中. NacosPropertySourceLocator 顺着前面的分析思路,我们很自然 ...

  7. Spring boot加载REACTIVE源码分析

    一,加载REACTIVE相关自动配置 spring boot通过判断含org.springframework.web.reactive.DispatcherHandler字节文件就确定程序类型是REA ...

  8. spring启动component-scan类扫描加载过程---源码分析

    http://blog.csdn.net/xieyuooo/article/details/9089441#comments

  9. Springboot学习05-自定义错误页面完整分析

    Springboot学习06-自定义错误页面完整分析 前言 接着上一篇博客,继续分析Springboot错误页面问题 正文 1-自定义浏览器错误页面(只要将自己的错误页面放在指定的路径下即可) 1-1 ...

随机推荐

  1. flask,gunicorn,supervisor,nginx配置服务器接口

    1,申请阿里云主机 2,apt-get update 3,apt-get install pip 4,pip install virtualenv 5,virtualenv venv 6,source ...

  2. java应用:向用户注册的邮箱发送邮件

    实现功能 忘记密码,注册成功等向用户发送验证码信息或注册信息. 业务流程 忘记密码: 1.验证邮箱是否注册过: 2.向邮箱发送验证码: 3.验证验证码是否正确: 4.重新设置密码: 我这里着重介绍发送 ...

  3. 计算:表中varchar类型的字段能容纳的最大字符数?

    建表语句: CREATE TABLE `test2` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `content` varchar(21842) NOT ...

  4. threading模块小结

    这篇文章是别人文章的一个观后小结,不是什么原创. 首先第一个例子: import threading import time def worker():     print "worker& ...

  5. Nodepad++ 进行数据分析操作

    查找: ^.*大师兄.*$ 替换为:(空)   如果不留空行: 查找: ^.*大师兄.*\r?\n   注意: Notepad++的[全部替换]受[方向]约束,所以如果想“向下”全部替换,要把光标放到 ...

  6. 阿里巴巴数据源Druid在tomcat中的配置

    这里只说需要的配置文件,不讲具体的项目,仅作为备忘. pom.xml文件添加 <!-- druid --> <dependency> <groupId>com.al ...

  7. Win7系统安装Centos7.0双系统(三)

    4.6语言选择 4.7安装信息设置,除以下几项改动其他都可默认. 软件选择(默认最小):带GUI的服务器或GNOME桌面,可根据使用需要选择安装软件. 磁盘分区:Linux默认可分为3个分区,分别是b ...

  8. [转][Echarts]俄罗斯方块

    app.title = '俄罗斯方块'; var refreshT,fallBlockT; var fallTimout; var speed = 1000, downSpeed = 30, nomr ...

  9. 常量&字符编码

    day1 name='Nod Chen' name2=name print('My name is ',name,name2) name='Luna zhou' print(name,name2) _ ...

  10. [UE4]VR成像原理

    一.双眼成像原理 二.3D电影成像原理 模拟人眼.用2个摄像机拍摄,模拟人的左眼和右眼 播放的时候2个投影仪分别同时播放左右摄像机拍摄到内容,观众带上3D眼镜,左眼只能看到左摄像机的内容(过滤右摄像机 ...