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

时间:2021-09-12 02:48:07

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

前沿

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

正文

1-Springboot错误页面展示

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

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