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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM