SpringBoot详细打印启动时异常堆栈信息

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

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内部提供的启动异常分析都是指定具体的异常类型实现的,最常见的一个错误就是端口号被占用(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}统一启动分析
 *
 * @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的泛型参数。

代码示例

如果您喜欢本篇文章请为源码仓库点个Star,谢谢!!!
本篇文章示例源码可以通过以下途径获取,源码分支为2.x,目录为spring-boot-failure-analyzer

相关文章
|
2月前
|
SQL 监控 Java
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
这篇文章介绍了如何在IDEA和Spring Boot中使用AOP技术实现日志信息的记录到数据库的详细步骤和代码示例。
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
|
2月前
|
前端开发 小程序 Java
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
本文详细介绍了如何在SpringBoot项目中统一处理接口返回结果及全局异常。首先,通过封装`ResponseResult`类,实现了接口返回结果的规范化,包括状态码、状态信息、返回信息和数据等字段,提供了多种成功和失败的返回方法。其次,利用`@RestControllerAdvice`和`@ExceptionHandler`注解配置全局异常处理,捕获并友好地处理各种异常信息。
230 0
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
|
2月前
|
前端开发 数据库
SpringBoot+Vue实现商品不能重复加入购物车、购物车中展示商品的信息、删除商品重点提示等操作。如何点击图片实现图片放大
这篇文章介绍了如何在SpringBoot+Vue框架下实现购物车功能,包括防止商品重复加入、展示商品信息、删除商品时的提示,以及点击图片放大的前端实现。
SpringBoot+Vue实现商品不能重复加入购物车、购物车中展示商品的信息、删除商品重点提示等操作。如何点击图片实现图片放大
|
2月前
|
JSON JavaScript 前端开发
基于SpringBoot + Vue实现单个文件上传(带上Token和其它表单信息)的前后端完整过程
本文介绍了在SpringBoot + Vue项目中实现单个文件上传的同时携带Token和其它表单信息的前后端完整流程,包括后端SpringBoot的文件上传处理和前端Vue使用FormData进行表单数据和文件的上传。
133 0
基于SpringBoot + Vue实现单个文件上传(带上Token和其它表单信息)的前后端完整过程
|
2月前
|
数据库
elementUi使用dialog的进行信息的添加、删除表格数据时进行信息提示。删除或者添加成功的信息提示(SpringBoot+Vue+MybatisPlus)
这篇文章介绍了如何在基于SpringBoot+Vue+MybatisPlus的项目中使用elementUI的dialog组件进行用户信息的添加和删除操作,包括弹窗表单的设置、信息提交、数据库操作以及删除前的信息提示和确认。
elementUi使用dialog的进行信息的添加、删除表格数据时进行信息提示。删除或者添加成功的信息提示(SpringBoot+Vue+MybatisPlus)
|
2月前
|
XML Java Maven
logback在springBoot项目中的使用 springboot中使用日志进行持久化保存日志信息
这篇文章详细介绍了如何在Spring Boot项目中使用logback进行日志记录,包括Maven依赖配置、logback配置文件的编写,以及实现的日志持久化和控制台输出效果。
logback在springBoot项目中的使用 springboot中使用日志进行持久化保存日志信息
|
2月前
|
消息中间件 Java 开发工具
【Azure 事件中心】Spring Cloud Stream Event Hubs Binder 发送Event Hub消息遇见 Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially 异常
【Azure 事件中心】Spring Cloud Stream Event Hubs Binder 发送Event Hub消息遇见 Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially 异常
|
2月前
|
Java Spring
【Azure 事件中心】Spring Boot 集成 Event Hub(azure-spring-cloud-stream-binder-eventhubs)指定Partition Key有异常消息
【Azure 事件中心】Spring Boot 集成 Event Hub(azure-spring-cloud-stream-binder-eventhubs)指定Partition Key有异常消息
|
2月前
|
Java Spring
【Azure 服务总线】Spring Cloud 的应用 使用Service Bus 引起 org.springframework.beans.BeanInstantiationException 异常,无法启动
【Azure 服务总线】Spring Cloud 的应用 使用Service Bus 引起 org.springframework.beans.BeanInstantiationException 异常,无法启动
|
2月前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常