SpringBoot之自定义启动异常堆栈信息打印方式

 更新时间:2021年08月09日 08:38:52   作者:weixin_36276193  
这篇文章主要介绍了SpringBoot之自定义启动异常堆栈信息打印方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

在SpringBoot项目启动过程中,当一些配置或者其他错误信息会有一些的规范的提示信息

***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

在SpringBoot 中其实现原理是什么,我们该如何自定义异常信息呢

1、SpringBoot异常处理的源码分析

在springboot启动的核心方法run中会加载所有的SpringBootExceptionReporter

 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
     new Class[] { ConfigurableApplicationContext.class }, context);

调用了getSpringFactoriesInstances方法

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  ClassLoader classLoader = getClassLoader();
  // Use names and ensure unique to protect against duplicates
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
 }

其主要通过Spring的Factories机制来加载

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  configureHeadlessProperty();
  SpringApplicationRunListeners listeners = getRunListeners(args);
  listeners.starting();
  try {
   ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
   ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
   configureIgnoreBeanInfo(environment);
   Banner printedBanner = printBanner(environment);
   context = createApplicationContext();
   exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
     new Class[] { ConfigurableApplicationContext.class }, context);
   prepareContext(context, environment, listeners, applicationArguments, printedBanner);
   refreshContext(context);
   afterRefresh(context, applicationArguments);
   stopWatch.stop();
   if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
   }
   listeners.started(context);
   callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
  //异常捕获中,向用户打印异常信息
   handleRunFailure(context, ex, exceptionReporters, listeners);
   throw new IllegalStateException(ex);
  }

  try {
   listeners.running(context);
  }
  catch (Throwable ex) {
   handleRunFailure(context, ex, exceptionReporters, null);
   throw new IllegalStateException(ex);
  }
  return context;
 }

在try catch中,catch会打印异常信息

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
   Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
  try {
   try {
    handleExitCode(context, exception);
    if (listeners != null) {
     listeners.failed(context, exception);
    }
   }
   finally {
    reportFailure(exceptionReporters, exception);
    if (context != null) {
     context.close();
    }
   }
  }
  catch (Exception ex) {
   logger.warn("Unable to close ApplicationContext", ex);
  }
  ReflectionUtils.rethrowRuntimeException(exception);
 }
 private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
  try {
   for (SpringBootExceptionReporter reporter : exceptionReporters) {
    if (reporter.reportException(failure)) {
     registerLoggedException(failure);
     return;
    }
   }
  }
  catch (Throwable ex) {
   // Continue with normal handling of the original failure
  }
  if (logger.isErrorEnabled()) {
   logger.error("Application run failed", failure);
   registerLoggedException(failure);
  }
 }

遍历exceptionReporters,打印日常信息

SpringBootExceptionReporter-

SpringBootExceptionReporter是一个回调接口,用于支持对SpringApplication启动错误的自定义报告。里面就一个报告启动失败的方法。

其实现类:org.springframework.boot.diagnostics.FailureAnalyzers

用于触发从spring.factories加载的FailureAnalyzer和FailureAnalysisReporter实例。

2、如何自定义异常信息

/**
 * <p>
 *
 * <p>
 *
 * @author: xuwd
 * @time: 2020/11/16 10:52
 */
public class WannaStopException extends RuntimeException {
}

自定义异常信息打印

public class StopFailureAnalyzer
        extends AbstractFailureAnalyzer<WannaStopException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, WannaStopException cause) {
        for (StackTraceElement stackTraceElement : cause.getStackTrace()) {
            if (stackTraceElement.getClassName().equals("com.pigx.demo.Config21")) {
                return new FailureAnalysis("A想停止", "别要A了", cause);
            }
        }
        return null;
    }
}

接下来令他生效,通过上面分析可以可看出需要通过AutoConfigurationImportSelector,类似于自定义SpringBoot Starter AutoConfiguration的形式,我们需要在META-INF/spring.factories文件内进行定义,如下所示:

https://juejin.im/post/6844903956208943111

接着在合适的地方抛出WannaStopException 异常

总结

在springboot 启动过程中会先对异常信息进行补捕获,对进行日志格式处理的日志进行处理;其核心是通过SpringBootExceptionReporter回调及sping-spi bean的管理。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • spring中@Autowire和@Resource的区别在哪里(推荐)

    spring中@Autowire和@Resource的区别在哪里(推荐)

    这篇文章主要介绍了spring中@Autowire和@Resource的区别在哪里?本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-02-02
  • springboot中如何配置LocalDateTime JSON返回时间戳

    springboot中如何配置LocalDateTime JSON返回时间戳

    这篇文章主要介绍了springboot中如何配置LocalDateTime JSON返回时间戳问题。具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • java统计汉字字数的方法示例

    java统计汉字字数的方法示例

    这篇文章主要介绍了java统计汉字字数的方法,结合实例形式分析了java正则判定、字符串遍历及统计相关操作技巧,需要的朋友可以参考下
    2017-05-05
  • SpringBoot入门实现第一个SpringBoot项目

    SpringBoot入门实现第一个SpringBoot项目

    今天我们一起来完成一个简单的SpringBoot(Hello World)。就把他作为你的第一个SpringBoot项目。具有一定的参考价值,感兴趣的可以了解一下
    2021-09-09
  • springboot docker原理及项目构建

    springboot docker原理及项目构建

    这篇文章主要介绍了springboot docker原理及项目构建,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • SpringBoot2.6.x默认禁用循环依赖后的问题解决

    SpringBoot2.6.x默认禁用循环依赖后的问题解决

    由于SpringBoot从底层逐渐引导开发者书写规范的代码,同时也是个忧伤的消息,循环依赖的应用场景实在是太广泛了,所以SpringBoot 2.6.x不推荐使用循环依赖,本文给大家说下SpringBoot2.6.x默认禁用循环依赖后的应对策略,感兴趣的朋友一起看看吧
    2022-02-02
  • 解决java.util.NoSuchElementException异常正确方法

    解决java.util.NoSuchElementException异常正确方法

    java.util.NoSuchElementException是Java中的一种异常,表示在迭代器或枚举中找不到元素,这篇文章主要给大家介绍了关于解决java.util.NoSuchElementException异常的相关资料,需要的朋友可以参考下
    2023-11-11
  • 教你通过B+Tree平衡多叉树理解InnoDB引擎的聚集和非聚集索引

    教你通过B+Tree平衡多叉树理解InnoDB引擎的聚集和非聚集索引

    大家都知道B+Tree是从二叉树演化而来,在这之前我们来先了解二叉树、平衡二叉树、平衡多叉树,这篇文章主要介绍了通过B+Tree平衡多叉树理解InnoDB引擎的聚集和非聚集索引,需要的朋友可以参考下
    2022-01-01
  • Java TreeMap升序|降序排列和按照value进行排序的案例

    Java TreeMap升序|降序排列和按照value进行排序的案例

    这篇文章主要介绍了Java TreeMap升序|降序排列和按照value进行排序的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • SpringBoot利用jackson格式化时间的三种方法

    SpringBoot利用jackson格式化时间的三种方法

    日常开发过程中经常会使用json进行数据的传输,这就涉及到了对象和json的相互转化,常用的解决方案有:Jackson(推荐)、谷歌的Gson、阿里的Fastjson,这篇文章主要给大家介绍了关于SpringBoot如何利用jackson格式化时间的相关资料,需要的朋友可以参考下
    2021-06-06

最新评论