SpringBoot詳細打印啓動時異常堆棧信息

SpringBoot在項目啓動時若是遇到異常並不能友好的打印出具體的堆棧錯誤信息,咱們只能查看到簡單的錯誤消息,以至於並不能及時解決發生的問題,針對這個問題SpringBoot提供了故障分析儀的概念(failure-analyzer),內部根據不一樣類型的異常提供了一些實現,咱們若是想自定義該怎麼去作?java

FailureAnalyzer

SpringBoot提供了啓動異常分析接口FailureAnalyzer,該接口位於org.springframework.boot.diagnosticspackage內。
內部僅提供一個分析的方法,源碼以下所示:spring

@FunctionalInterface
public interface FailureAnalyzer {

    /**
     * Returns an analysis of the given {@code failure}, or {@code null} if no analysis
     * was possible.
     * @param failure the failure
     * @return the analysis or {@code null}
     */
    FailureAnalysis analyze(Throwable failure);

}

該接口會把遇到的異常對象實例Throwable failure交付給實現類,實現類進行自定義處理。springboot

AbstractFailureAnalyzer

AbstractFailureAnalyzerFailureAnalyzer的基礎實現抽象類,實現了FailureAnalyzer定義的analyze(Throwable failure)方法,並提供了一個指定異常類型的抽象方法analyze(Throwable rootFailure, T cause),源碼以下所示:ide

public abstract class AbstractFailureAnalyzer<T extends Throwable> implements FailureAnalyzer {

    @Override
    public FailureAnalysis analyze(Throwable failure) {
        T cause = findCause(failure, getCauseType());
        if (cause != null) {
            return analyze(failure, cause);
        }
        return null;
    }

    /**
     * Returns an analysis of the given {@code rootFailure}, or {@code null} if no
     * analysis was possible.
     * @param rootFailure the root failure passed to the analyzer
     * @param cause the actual found cause
     * @return the analysis or {@code null}
     */
    protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause);

    /**
     * Return the cause type being handled by the analyzer. By default the class generic
     * is used.
     * @return the cause type
     */
    @SuppressWarnings("unchecked")
    protected Class<? extends T> getCauseType() {
        return (Class<? extends T>) ResolvableType.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric();
    }

    @SuppressWarnings("unchecked")
    protected final <E extends Throwable> E findCause(Throwable failure, Class<E> type) {
        while (failure != null) {
            if (type.isInstance(failure)) {
                return (E) failure;
            }
            failure = failure.getCause();
        }
        return null;
    }

}

經過AbstractFailureAnalyzer源碼咱們能夠看到,它在實現於FailureAnalyzer的接口方法內進行了特殊處理,根據getCauseType()方法獲取當前類定義的第一個泛型類型,也就是咱們須要分析的指定異常類型測試

獲取泛型異常類型後根據方法findCause判斷Throwable是否與泛型異常類型匹配,若是匹配直接返回給SpringBoot進行註冊處理。spa

SpringBoot提供的分析實現

SpringBoot內部經過實現AbstractFailureAnalyzer抽象類定義了一系列的針對性異常類型的啓動分析,以下圖所示:code

圖片描述

指定異常分析

SpringBoot內部提供的啓動異常分析都是指定具體的異常類型實現的,最多見的一個錯誤就是端口號被佔用(PortInUseException),雖然SpringBoot內部提供一個這個異常的啓動分析,咱們也是能夠進行替換這一異常分析的,咱們只須要建立PortInUseException異常的AbstractFailureAnalyzer,而且實現類註冊給SpringBoot便可,實現自定義以下所示:對象

/**
 * 端口號被佔用{@link PortInUseException}異常啓動分析
 *
 * @author 恆宇少年
 */
public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(PortInUseFailureAnalyzer.class);

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
        logger.error("端口被佔用。", cause);
        return new FailureAnalysis("端口號:" + cause.getPort() + "被佔用", "PortInUseException", rootFailure);
    }
}

註冊啓動異常分析

在上面咱們只是編寫了指定異常啓動分析,咱們接下來須要讓它生效,這個生效方式比較特殊,相似於自定義SpringBoot Starter AutoConfiguration的形式,咱們須要在META-INF/spring.factories文件內進行定義,以下所示:blog

org.springframework.boot.diagnostics.FailureAnalyzer=\
  org.minbox.chapter.springboot.failure.analyzer.PortInUseFailureAnalyzer

那咱們爲何須要使用這種方式定義呢?繼承

項目啓動遇到的異常順序不能肯定,極可能在Spring IOC並未執行初始化以前就出現了異常,咱們不能經過@Component註解的形式使其生效,因此SpringBoot提供了經過spring.factories配置文件的方式定義。

啓動異常分析繼承關係

自定義的運行異常通常都是繼承自RuntimeException,若是咱們定義一個RuntimeException的異常啓動分析實例會是什麼效果呢?

/**
 * 項目啓動運行時異常{@link RuntimeException}統一啓動分析
 *
 * @author 恆宇少年
 */
public class ProjectBootUnifiedFailureAnalyzer extends AbstractFailureAnalyzer<RuntimeException> {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(ProjectBootUnifiedFailureAnalyzer.class);

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, RuntimeException cause) {
        logger.error("遇到運行時異常", cause);
        return new FailureAnalysis(cause.getMessage(), "error", rootFailure);
    }
}

將該類也一併註冊到spring.factories文件內,以下所示:

org.springframework.boot.diagnostics.FailureAnalyzer=\
  org.minbox.chapter.springboot.failure.analyzer.PortInUseFailureAnalyzer,\
  org.minbox.chapter.springboot.failure.analyzer.ProjectBootUnifiedFailureAnalyzer

運行項目並測試端口號被佔用異常咱們會發現,並無執行ProjectBootUnifiedFailureAnalyzer內的analyze方法,而是繼續執行了PortInUseFailureAnalyzer類內的方法。

那咱們將PortInUseFailureAnalyzer這個啓動分析從spring.factories文件內暫時刪除掉,再來運行項目咱們會發現這時倒是會執行ProjectBootUnifiedFailureAnalyzer類內分析方法。

總結

根據本章咱們瞭解了SpringBoot提供的啓動異常分析接口以及基本抽象實現類的運做原理,並且啓動異常分析存在分析泛型異常類的上下級繼承關係,異常子類的啓動分析會覆蓋掉異常父類的啓動分析,若是你想包含所有異常的啓動分析能夠嘗試使用Exception做爲AbstractFailureAnalyzer的泛型參數。

相關文章
相關標籤/搜索