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是一个回调接口,用于支持对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文件内进行定义,如下所示:

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

总结

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

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

(0)

相关推荐

  • Springboot之自定义全局异常处理的实现

    前言: 在实际的应用开发中,很多时候往往因为一些不可控的因素导致程序出现一些错误,这个时候就要及时把异常信息反馈给客户端,便于客户端能够及时地进行处理,而针对代码导致的异常,我们一般有两种处理方式,一种是throws直接抛出,一种是使用try..catch捕获,一般的话,如果逻辑的异常,需要知道异常信息,我们往往选择将异常抛出,如果只是要保证程序在出错的情况下 依然可以继续运行,则使用try..catch来捕获. 但是try..catch会导致代码量的增加,让后期我们的代码变得臃肿且难以维护.当

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

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

  • Spring Boot详细打印启动时异常堆栈信息详析

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

  • SpringBoot打印详细启动异常信息

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

  • 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 liste

  • Springboot之修改启动端口的两种方式(小结)

    Springboot启动的时候,端口的设定默认是8080,这肯定是不行的,我们需要自己定义端口,Springboot提供了两种方式,第一种,我们可以通过application.yml配置文件配置,第二种,可以通过代码里面指定,在开发中,建议使用修改application.yml的方式来修改端口. 代码地址 #通过yml配置文件的方式指定端口地址 https://gitee.com/yellowcong/springboot-demo/tree/master/springboot-demo2 #硬

  • Logger.error打印错误异常的详细堆栈信息

    一.问题场景 使用Logger.error方法时只能打印出异常类型,无法打印出详细的堆栈信息,使得定位问题变得困难和不方便. 二.先放出结论 Logger类下有多个不同的error方法,根据传入参数的个数及类型的不同,自动选择不同的重载方法. 当error(Object obj)只传入一个参数时会将异常对象作为Object使用,并最终当做String打印出来,当使用两个参数error(String message, Throwable t),且第二个参数为Throwable时,才会将完整的异常堆

  • 浅谈log4j 不打印异常堆栈

    本文研究的主要是log4j 不打印异常堆栈的相关内容,具体如下. 最近在线上系统的错误日志中发现了一个现象: 代码里用log4j打印系统运行时异常堆栈信息,在错误日志中无法看到堆栈信息,只有异常信息.这对于程序员来说是一个打击,没有堆栈信息何从查bug啊. [01-15 11:29:26] [ERROR] [org.apache.thrift.server.AbstractNonblockingServer$FrameBuffer:524] Unexpected throwable while

  • 如何在SpringBoot中使用logback优化异常堆栈的输出详解

    目录 一.背景 二.需求 三.使用的技术 四.技术实现 1.引入依赖 2.代码实现 3.使用 ShortenedThrowableConverter 来优化异常堆栈 4.查看运行结果 五.完整代码 六.参考文档 总结 一.背景 在我们在编写程序的过程中,无法保证自己的代码不抛出异常.当我们抛出异常的时候,通常会将整个异常堆栈的信息使用日志记录下来.通常一整个异常堆栈的信息是比较多的,而且存在一些没用的信息.那么我们如何优化一些异常堆栈的信息打印,过滤掉不必要的信息呢? 二.需求 1.现有的异常堆

  • Python实现自定义异常堆栈信息的示例代码

    当我们的程序报错时,解释器会将整个异常的堆栈信息全部输出出来,举个例子: def foo():     raise RuntimeError("抛一个异常") def bar():     foo() def main():     bar() main() 如果执行这段代码,会得到以下报错信息: 解释器会将异常产生的整个调用链都给打印出来,那么问题来了,我们能不能自定义这些报错信息呢? 答案是可以的,我们只要拿到这些报错信息,然后再进行修改即可.那么如何才能拿到呢?显然需要借助于 t

  • Python捕获异常堆栈信息的几种方法(小结)

    程序出错的时候,我们往往需要根据异常信息来找到具体出错的代码.简单地用print打印异常信息并不能很好地追溯出错的代码: # -*- coding: utf-8 -*- def foo(a, b): c = a + b raise ValueError('test') return c def bar(a): print('a + 100:', foo(a, 100)) def main(): try: bar(100) except Exception as e: print(repr(e))

随机推荐