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

    参考资料:

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

  • 相关阅读:
    0121 集合类 ArrayList 的练习
    0121 有关接口的使用练习
    泛型相关知识
    0120 父类与子类创建、重写及转型练习
    0118练习 单例模式
    java设计模式 略版
    0117 面向对象OOP有关方法、类、构造方法及权限修饰符的练习
    0115 创建类并调用
    [luogu P2586] GCD 解题报告 (莫比乌斯反演|欧拉函数)
    POJ1284 Primitive Roots (原根)
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/10144151.html
Copyright © 2011-2022 走看看