SpringBoot在项目启动时如果遇到异常并不能友好的打印出具体的堆栈错误信息,我们只能查看到简单的错误消息,以致于并不能及时解决发生的问题,针对这个问题SpringBoot提供了故障分析仪的概念(failure-analyzer),内部根据不同类型的异常提供了一些实现,我们如果想自定义该怎么去做?

FailureAnalyzer

  SpringBoot提供了启动异常分析接口FailureAnalyzer,该接口位于org.springframework.boot.diagnosticspackage内。内部仅提供一个分析的方法,源码如下所示:

@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交付给实现类,实现类进行自定义处理。

AbstractFailureAnalyzer

  AbstractFailureAnalyzerFailureAnalyzer的基础实现抽象类,实现了FailureAnalyzer定义的analyze(Throwable failure)方法,并提供了一个指定异常类型的抽象方法analyze(Throwable rootFailure, T cause),源码如下所示:


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进行注册处理。

SpringBoot提供的分析实现

SpringBoot内部通过实现AbstractFailureAnalyzer抽象类定义了一系列的针对性异常类型的启动分析,如下图所示:

springboot-failure-analyzer.png

指定异常分析

  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文件内进行定义,如下所示:

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

那我们为什么需要使用这种方式定义呢?

  项目启动遇到的异常顺序不能确定,很可能在Spring IOC并未执行初始化之前就出现了异常,我们不能通过@Component注解的形式使其生效,所以SpringBoot提供了通过spring.factories配置文件的方式定义。

启动异常分析继承关系

  自定义的运行异常一般都是继承自RuntimeException,如果我们定义一个RuntimeException的异常启动分析实例会是什么效果呢?

/**
* 项目启动运行时异常{@link RuntimeException}统一启动分析
*/
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的泛型参数。

SpringBoot-技术专区-详细打印启动时异常堆栈信息的更多相关文章

  1. [Java]借助PrintWriter类和StringWriter类,取出异常堆栈信息放入字符串中

    在程序开发中,有时我们不仅需要将异常堆栈信息打印在控制台里或是log里,可能还需要将它存在String中,再送到合适的地方,如错误页面,数据库等. 要取异常堆栈信息,具体的函数就是: /** * Ge ...

  2. log显示error时的堆栈信息理解和分析

    error显示的log堆栈信息,是从最深层(最内层)的堆栈信息开始由内向外打印的. error显示的log堆栈信息,是从最深层(最内层)的堆栈信息开始由内向外打印的. error显示的log堆栈信息, ...

  3. springboot hikari 连接池 在启动时未初始化数据库连接问题

    在启动Springboot 项目时 2019-11-18 21:32:38.223 INFO 1080 --- [on(4)-127.0.0.1] o.s.web.servlet.Dispatcher ...

  4. 打印Java异常堆栈信息

    背景 在开发Java应用程序的时候,遇到程序抛异常,我们通常会把抛异常时的运行时环境保存下来(写到日志文件或者在控制台中打印出来).这样方便后续定位问题. 需要记录的运行时环境包含两部分内容:抛异常时 ...

  5. 【Java】 NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException、ArrayIndexOutOfBoundsException、ArrayStoreException、ArithmeticException等没有异常堆栈信息

    今天工作中,临时Fix一个bug,一看日志“java.lang.ClassCastException: null”相当懵逼,没有详细堆栈信息,这咋整.虽然根据上下文可以推测代码的大致位置,但不敢拍板确 ...

  6. logger.error打印完整的错误堆栈信息

    使用Spring Boot项目中的日志打印功能的时候,发现调用Logger.errror()方法的时候不能完全地打印出网站的错误堆栈信息,只能打印出这个错误是一个什么错误. 为什么呢,原因在于这个方法 ...

  7. Java获取异常堆栈信息

    方法一: public static String getStackTrace(Throwable t) { StringWriter sw = new StringWriter(); PrintWr ...

  8. Logger.error方法之打印错误异常的详细堆栈信息

    一.问题场景 使用Logger.error方法时只能打印出异常类型,无法打印出详细的堆栈信息,使得定位问题变得困难和不方便. 二.先放出结论 Logger类下有多个不同的error方法,根据传入参数的 ...

  9. log4j打印错误异常的详细堆栈信息

    一.问题场景 使用Logger.error方法时只能打印出异常类型,无法打印出详细的堆栈信息,使得定位问题变得困难和不方便. 二.先放出结论 Logger类下有多个不同的error方法,根据传入参数的 ...

随机推荐

  1. 微信支付签名算法JavaScript版,参数名ASCII码从小到大排序;0,A,B,a,b;

    // 支付md5加密获取sign paysignjs: function (jsonobj) { var signstr = this.obj2str(jsonobj) signstr = signs ...

  2. thinkphp 系统变量

    一.查看可用变量 dump($_SERVER); 可以直接在html输出系统变量的值 <p>{$Think.server.HTTP_HOST}</p>. 二.环境变量 1.查看 ...

  3. pandas.Series函数用法

    class pandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False) e.g., ...

  4. margin 和padding 的区别

    margin是用来隔开元素与元素的间距:padding是用来隔开元素与内容的间隔.margin用于布局分开元素使元素与元素互不相干: padding用于元素与内容之间的间隔,让内容(文字)与(包裹)元 ...

  5. php substr_replace()函数 语法

    php substr_replace()函数 语法 作用:替换字符串中某串为另一个字符串大理石平台价格 语法:substr_replace(string,replacement,start,lengt ...

  6. MapServer教程2

    第二章 Tutorial 教程 MapServer Tutorial MapServer教程 Tutorial background 教程背景 Section 1: Static Maps and t ...

  7. Fiddler抓包工具-拦截,断点

    1.拦截网页 先拦截请求-F11设置拦截 -F12抓包 2.修改金额 宽带 下行:100M上行:8M 一般家用的都是下行高,企业用都是上下行对等.企业要搭建网站就需要上行. 我们去访问网站,服务器根据 ...

  8. 一个最最简单的 log4j 的 入门级使用案例

    看了比较多的文档和博客,感觉这篇博客写得比较好,比较容易懂,先 mark 一下,回头做一个记录. 文章1:http://www.cnblogs.com/rushoooooo/archive/2011/ ...

  9. 用 margin 还是用 padding ?

    边界(margin):元素周围生成额外的空白区."空白区"通常是指其他元素不能出现且父元素背景可见的区域. 补白(padding):补白位于元素框的边界与内容区之间.很自然,用于影 ...

  10. xiugai-去除js注释

    <div class="myLoading"> <div class="svg-wrap"> <svg width="8 ...