首页 > 编程语言 > SpringBoot之自定义启动异常堆栈信息打印方式
2022
02-10

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的管理。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持自学编程网。

编程技巧