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 ...
随机推荐
- flask,gunicorn,supervisor,nginx配置服务器接口
1,申请阿里云主机 2,apt-get update 3,apt-get install pip 4,pip install virtualenv 5,virtualenv venv 6,source ...
- java应用:向用户注册的邮箱发送邮件
实现功能 忘记密码,注册成功等向用户发送验证码信息或注册信息. 业务流程 忘记密码: 1.验证邮箱是否注册过: 2.向邮箱发送验证码: 3.验证验证码是否正确: 4.重新设置密码: 我这里着重介绍发送 ...
- 计算:表中varchar类型的字段能容纳的最大字符数?
建表语句: CREATE TABLE `test2` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `content` varchar(21842) NOT ...
- threading模块小结
这篇文章是别人文章的一个观后小结,不是什么原创. 首先第一个例子: import threading import time def worker(): print "worker& ...
- Nodepad++ 进行数据分析操作
查找: ^.*大师兄.*$ 替换为:(空) 如果不留空行: 查找: ^.*大师兄.*\r?\n 注意: Notepad++的[全部替换]受[方向]约束,所以如果想“向下”全部替换,要把光标放到 ...
- 阿里巴巴数据源Druid在tomcat中的配置
这里只说需要的配置文件,不讲具体的项目,仅作为备忘. pom.xml文件添加 <!-- druid --> <dependency> <groupId>com.al ...
- Win7系统安装Centos7.0双系统(三)
4.6语言选择 4.7安装信息设置,除以下几项改动其他都可默认. 软件选择(默认最小):带GUI的服务器或GNOME桌面,可根据使用需要选择安装软件. 磁盘分区:Linux默认可分为3个分区,分别是b ...
- [转][Echarts]俄罗斯方块
app.title = '俄罗斯方块'; var refreshT,fallBlockT; var fallTimout; var speed = 1000, downSpeed = 30, nomr ...
- 常量&字符编码
day1 name='Nod Chen' name2=name print('My name is ',name,name2) name='Luna zhou' print(name,name2) _ ...
- [UE4]VR成像原理
一.双眼成像原理 二.3D电影成像原理 模拟人眼.用2个摄像机拍摄,模拟人的左眼和右眼 播放的时候2个投影仪分别同时播放左右摄像机拍摄到内容,观众带上3D眼镜,左眼只能看到左摄像机的内容(过滤右摄像机 ...