Spring Boot 2.x Whitelabel Error Page 源碼分析以及解決方案

在使用 Spring Boot 若是出現錯誤會出現 Whitelabel Error Page 頁面,這個是 Spring Boot 默認處理錯誤的一個頁面,是一硬編碼的形式建立的。咱們能夠替換調,使用本身的error頁面,而且美化它。html

網上也有不少相似的文章,不過看了不少有的不全面、有的根本就是錯誤的(好比設置server.error.whitelabel.enabled=false添加error.html,這是有前提的), 今天咱們從源碼的角度來分析它並給出解決方案。java

首選Spring Boot 若是出現錯誤,好比:500503404等,均是有 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 處理的,咱們也能夠重寫它(若是你重寫了它,你會驚奇的發現:下面的內容是多餘的,哈哈)實現本身的邏輯;
web

從圖上能夠看出 Spring Boot 想的很周全,同步和異步均有處理,這裏咱們只討論同步的問題, 由於只有同步會出現Whitelabel Error Pagespring

咱們定位到解析視圖的那句代碼緩存

ModelAndView modelAndView = resolveErrorView(request, response, status, model);

resolveErrorView 是由父類AbstractErrorControllers實現 app

ErrorViewResolver 的默認實現是 DefaultErrorViewResolver
異步

resolve方法中咱們能夠拆分一下幾步 一、找到可用的模版引擎提供者ide

假如出現的錯誤是 500 則此時errorViewName="error/500" spring-boot

  • 查找邏輯交給了TemplateAvailabilityProviders類的getProvider方法,此方法會緩存已經存在的模板引擎提供者, 具體由findProvider去查找,若findProvider也沒找到會默認註冊一個NoTemplateAvailabilityProvider ui

  • 注意 this.providers 這是 Spring Boot 在容器啓動的時候就會添加的,經過模板引擎類名是否存在來判斷是否添加某個的模板引擎提供者,默認Spring Boot 會註冊一下幾個模板引擎提供者;

  • 加載模板引擎提供者

loadFactories 加載的是spring-boot-autoconfigure-2.xxxx.jar下面的META-INF/spring.factories, 此方法能夠獲取父類全部的實現類

  • 這裏以ThymeleafTemplateAvailabilityProvider爲例
    從圖中能夠看出找的是classpath:/templates/error/500.html 因此若出現500錯誤咱們能夠在src/main/resources/ 下新建目錄templates/error在新建一個thymeleaf500.html模板就能夠了;404錯誤同理

  • 從這裏咱們不難看出若想成功找到錯誤模板必須知足一下條件

  1. 類路徑存在模板引擎的jar
  2. classpath下存在存在當前錯誤模板(例:500.html)

二、若第1步中的provider找到則不用繼續下一步,直接返回找到的視圖

三、若第1步中沒有知足條件的provider則交給resolveResource處理;咱們來看看resolveResource的邏輯
這一步主要的直接獲取靜態的html;通常500404能夠直接使用靜態的HTML資源展現一個友好的提示便可,因此這裏直接獲取靜態的資源文件 默認查找位置以下

能夠經過配置改變

假如出現的錯誤是500,查找結果如圖;咱們只須要建立資源文件,就能夠訪問<staticLocations>/error/5xx.html

四、若是以上三步都沒知足,怎麼辦?從BasicErrorController 中能夠看到最後會有個error默認view,就會去找classpath:/templates/error.html,處理邏輯同上;若是依然找不到Spring Boot 就會默認交給 ErrorMvcAutoConfiguration中的 StaticView 去渲染

前提是 server.error.whitelabel.enabled=true

private static class StaticView implements View {

    private static final Log logger = LogFactory.getLog(StaticView.class);

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        if (response.isCommitted()) {
            String message = getMessage(model);
            logger.error(message);
            return;
        }
        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(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(htmlEscape(model.get("error"))).append(", status=")
                .append(htmlEscape(model.get("status"))).append(").</div>");
        if (message != null) {
            builder.append("<div>").append(htmlEscape(message)).append("</div>");
        }
        if (trace != null) {
            builder.append("<div style='white-space:pre-wrap;'>")
                    .append(htmlEscape(trace)).append("</div>");
        }
        builder.append("</body></html>");
        response.getWriter().append(builder.toString());
    }

    private String htmlEscape(Object input) {
        return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
    }

    private String getMessage(Map<String, ?> model) {
        Object path = model.get("path");
        String message = "Cannot render error page for request [" + path + "]";
        if (model.get("message") != null) {
            message += " and exception [" + model.get("message") + "]";
        }
        message += " as the response has already been committed.";
        message += " As a result, the response may have the wrong status code.";
        return message;
    }

    @Override
    public String getContentType() {
        return "text/html";
    }

}

這就是 Whitelabel Error Page 來源

綜上所述,共有以下解決方案

若項目中使用了模板引擎 好比thymeleaf freemarker
此時server.error.whitelabel.enabled關閉和開啓無關

  1. classpath下新建模板 /templates/error/5xx.html /templates/error/4xx.html
  2. classpath下新建靜態資源 /<staticlocation>/error/5xx.html /<staticlocation>/error/4xx.html
  3. classpath下直接新建模板 /templates/error.html

若項目中沒有任何模板引擎

  1. 設置server.error.whitelabel.enabled=true使用內置view渲染,出現Whitelabel Error Page
  2. server.error.whitelabel.enabled=false將不會有任何信息

若以上有不懂或錯誤,能夠留言 碼專不易,轉載註明出處

關注WX公衆號,精彩不容錯過

相關文章
相關標籤/搜索