好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

?

1

2

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter. class ,

     new Class[] { ConfigurableApplicationContext. class }, context);

调用了getSpringFactoriesInstances方法

?

1

2

3

4

5

6

7

8

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机制来加载

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

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会打印异常信息

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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);

  }

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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、如何自定义异常信息

?

1

2

3

4

5

6

7

8

9

10

/**

  * <p>

  *

  * <p>

  *

  * @author: xuwd

  * @time: 2020/11/16 10:52

  */

public class WannaStopException extends RuntimeException {

}

自定义异常信息打印

?

1

2

3

4

5

6

7

8

9

10

11

12

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

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

原文链接:https://blog.csdn.net/weixin_36276193/article/details/109717547

查看更多关于SpringBoot之自定义启动异常堆栈信息打印方式的详细内容...

  阅读:32次