Springboot学习04-默认错误页面加载机制源码分析
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-ErrorMvcAutoConfiguration配置类
package org.springframework.boot.autoconfigure.web.servlet.error;
@Configuration
@AutoConfigureBefore({WebMvcAutoConfiguration.class})//在WebMvcAutoConfiguration 配置之前完成peizhi
public class ErrorMvcAutoConfiguration { //注册了一个 专门收集 error 发生时错误信息的bean
//DefaultErrorAttributes 实现了HandlerExceptionResolver, 通过对异常的处理, 填充 错误属性 ErrorAttributes 。 这个是boot 中的 controller 出现异常的时候会使用到的
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
} //注册 BasicErrorController。 BasicErrorController 完成对所有 controller 发生异常情况的处理, 包括 异常和 4xx, 5xx 子类的;处理默认/error请求
@Bean
@ConditionalOnMissingBean(
value = {ErrorController.class},
search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}
//注册 错误页面的 定制器
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
} } //1-1-ErrorMvcAutoConfigurationde配置类的内部类:WhitelabelErrorViewConfiguration
@Configuration
@ConditionalOnProperty(
prefix = "server.error.whitelabel",
name = {"enabled"},
matchIfMissing = true
)
@Conditional({ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition.class})
protected static class WhitelabelErrorViewConfiguration {
//StaticView就是ErrorMvcAutoConfigurationde配置类的内部类:StaticView
private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView(); protected WhitelabelErrorViewConfiguration() {
} @Bean(name = {"error"})
@ConditionalOnMissingBean(name = {"error"})
public View defaultErrorView() {
return this.defaultErrorView;
} @Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(2147483637);
return resolver;
}
} //1-2-ErrorMvcAutoConfigurationde配置类的内部类:StaticView
//WhitelabelErrorViewConfiguration 逻辑
//1-WhitelabelErrorViewConfiguration 注册了 一个View, 同时 注册了BeanNameViewResolver,如果之前没有注册的话。
//2-BeanNameViewResolver 也是可以对View 进行处理的, 它的处理方式是根据 view 的name 查找对应的bean。 这里 defaultErrorView 也是一个bean, 其名字是 error。 即:如果发现请求是 /error, 那么如果其他 ViewResolver 处理不了, 就BeanNameViewResolver 来处理,BeanNameViewResolver 把defaultErrorView 渲染到浏览器
//3-可以看到, defaultErrorView 通常是异常处理的最后一个围墙, 因为 BeanNameViewResolver的优先级比较低defaultErrorView(实际就是StaticView)实现了 View , 主要就是完成了对 页面的渲染, 提供了一个 render 方法。
private static class StaticView implements View {
private static final Log logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class); private StaticView() {
} public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (response.isCommitted()) {
String message = this.getMessage(model);
logger.error(message);
} else {
StringBuilder builder = new StringBuilder();
Date timestamp = (Date)model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(this.getContentType());
} 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>");
if (message != null) {
builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
} if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
} builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
}
} //1-3-ErrorMvcAutoConfigurationde配置类的内部类:ErrorPageCustomizer
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;
}
//把 /error 这样的errorpage 注册到了servlet容器,使得它异常的时候,会转发到/error
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
} public int getOrder() {
return 0;
}
} //2-1-BasicErrorController类
package org.springframework.boot.autoconfigure.web.servlet.error;
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController { //当请求出现错误时,浏览器响应 ModelAndView errorHtml
@RequestMapping(produces = {"text/html"})
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
//示例:status = 404 NOT_FOUND
HttpStatus status = this.getStatus(request);
//这里的 model 是相关错误信息;示例:model={"timestamp":"Thu Dec 20 09:12:09 CST 2018","status" :"404","error": "Not Found","message": "No message available","path" :"/111"}
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//这个完成了具体的处理过程;获取视图
//这里的resolveErrorView 是AbstractErrorController.AbstractErrorController
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
//如果modelAndView=null;则返回 new ModelAndView("error", model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
} //这里相对上面的方法,简单很多,它不会去使用 viewResolver 去处理, 因为它不需要任何的 view ,而是直接返回 text 格式数据,而不是 html 格式数据
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
} } //2-2-AbstractErrorController抽象类
package org.springframework.boot.autoconfigure.web.servlet.error;
public abstract class AbstractErrorController implements ErrorController { protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//这个errorViewResolvers 就是ErrorMvcAutoConfiguration.errorViewResolvers 成员变量,errorViewResolvers包含DefaultErrorViewResolver
Iterator var5 = this.errorViewResolvers.iterator();
ModelAndView modelAndView;
do {
if (!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null); return modelAndView;
}
} //3-DefaultErrorViewResolver类
package org.springframework.boot.autoconfigure.web.servlet.error;
// DefaultErrorViewResolver 逻辑
//1-DefaultErrorViewResolver 作为 ErrorViewResolver 注入到 ErrorMvcAutoConfiguration 的构造中去;而 errorViewResolvers 其实就是直接 交给了 BasicErrorController。 也就是说, BasicErrorController 处理错误的时候,会使用 DefaultErrorViewResolver 提供的内容来进行页面渲染。
//2-DefaultErrorViewResolver是一个纯 boot 的内容,专门处理发生 error时候的 view
//3-当请求需要一个error view, 就先去 error/ 目录下面去找 error/ + viewName + .html 的文件(这里的viewName通常是 404 ,500 之类的错误的response status code); 找到了 就直接展示(渲染)它。 否则就尝试去匹配4xx, 5xx,然后去找error/4xx.html或者 error/5xx.html两个页面,找到了就展示它。
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<Series, String> SERIES_VIEWS;
private ApplicationContext applicationContext;
private final ResourceProperties resourceProperties; public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
} return modelAndView;
} private ModelAndView resolve(String viewName, Map<String, Object> model) {
//示例:errorViewName = error/404
String errorViewName = "error/" + viewName;
//示例:从applicationContext中获取viewName="error/404"的可用模版
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//如果provider=null,则返回this.resolveResource(errorViewName, model)
//this.resolveResource<--见下面代码-->
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
} //获取静态资源视图
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
// 静态的 location 包括 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
String[] var3 = this.resourceProperties.getStaticLocations();
int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5]; try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
//资源必须要存在, 才会返回
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception var8) {
;
}
}
//如果各个静态目录下都没有找到那个html文件,那么就还是 返回null, 交给白标吧
return null;
} 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);
} private static class HtmlResourceView implements View {
private Resource resource; HtmlResourceView(Resource resource) {
this.resource = resource;
} public String getContentType() {
return "text/html";
} public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType(this.getContentType());
FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
}
}
}
参考资料:
1-https://www.cnblogs.com/FlyAway2013/p/7944568.html
Springboot学习04-默认错误页面加载机制源码分析的更多相关文章
- Springboot 加载配置文件源码分析
Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...
- ElasticSearch 启动时加载 Analyzer 源码分析
ElasticSearch 启动时加载 Analyzer 源码分析 本文介绍 ElasticSearch启动时如何创建.加载Analyzer,主要的参考资料是Lucene中关于Analyzer官方文档 ...
- 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)
目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...
- springboot Properties加载顺序源码分析
关于properties: 在spring框架中properties为Environment对象重要组成部分, springboot有如下几种种方式注入(优先级从高到低): 1.命令行 java -j ...
- jQuery实现DOM加载方法源码分析
传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... } 但 ...
- Spring Cloud Nacos实现动态配置加载的源码分析
理解了上述Environment的基本原理后,如何从远程服务器上加载配置到Spring的Environment中. NacosPropertySourceLocator 顺着前面的分析思路,我们很自然 ...
- Spring boot加载REACTIVE源码分析
一,加载REACTIVE相关自动配置 spring boot通过判断含org.springframework.web.reactive.DispatcherHandler字节文件就确定程序类型是REA ...
- spring启动component-scan类扫描加载过程---源码分析
http://blog.csdn.net/xieyuooo/article/details/9089441#comments
- Springboot学习05-自定义错误页面完整分析
Springboot学习06-自定义错误页面完整分析 前言 接着上一篇博客,继续分析Springboot错误页面问题 正文 1-自定义浏览器错误页面(只要将自己的错误页面放在指定的路径下即可) 1-1 ...
随机推荐
- hibernate---session查询
一.hql语句查询(适合多表) public class MyTest { public static void main(String[] args) { //查询集合 Session sessio ...
- python------Socket网略编程+动态导入模块
上节课程回顾: 静态变量:与类无关,不能访问类里的任何属性和方法. 类方法:只能访问类变量. 属性:把一个方法变成静态属性, 反射: __new__:先于__init__执行: __call__: c ...
- 全志A33开发板Linux内核定时器编程
开发平台 * 芯灵思SinlinxA33开发板 淘宝店铺: https://sinlinx.taobao.com/ 嵌入式linux 开发板交流 QQ:641395230 Linux 内核定时器是内核 ...
- POJ1821 Fence
题意 Language:Default Fence Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 6478 Accepted: ...
- PythonStudy——字符串类型 String type
# 1.定义# 需求:你是"好学生" s1 = "你是\"好学生\"" print(s1) # 可以通过引号的嵌套,使内部不同的引号在不转义 ...
- 集合总结三(HashMap的实现原理)
一.概述 二话不说,一上来就点开源码,发现里面有一段介绍如下: Hash table based implementation of the Map interface. This implement ...
- Java day1
1. 学习java,首先是jdk的安装,JDK是 Java 语言的软件开发工具包,主要用于移动设备.嵌入式设备上的java应用程序.JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+ ...
- 将string转为同名类名,方法名。(c#反射)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace stri ...
- JavaScript中的匿名函数遇上!会怎么样
通常,我们声明一个函数test){},可以通过test()来调用这个函数.但是,如果我们在这个函数声明的末尾加上(),解析器是无法理解的. function test(){ console.log(' ...
- problem:vue组件局部刷新,在组件销毁(destroyed)时取消刷新无效问题
场景: 一个群发消息列表(数组) 列表下有多条消息(元素) 每条正在发送的消息数据状态需要实时刷新,发送完成时需要显示成功提示符合且不需要刷新,然后3秒消失.首次显示列表时,已经成功的状态不显示这个成 ...