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

 

posted on 2018-12-20 20:56  我不吃番茄  阅读(2609)  评论(1编辑  收藏  举报