The Spring Boot framework may be abnormal during startup for a variety of reasons. Spring Boot designs a set of exception handling methods to present exceptions in a friendly manner and facilitate troubleshooting. This solution is consistent with Spring’s single-responsibility, decoupled, and extensible features. Let’s take a look at how Spring Boot handles framework exceptions.

The core idea

If we were to design an exception architecture, the easiest thing to think of would be to inherit RuntimeException, override the constructor of the parent class, and add some custom descriptions. When we throw an exception, we can use the description we add to determine the cause of the exception.

PortInUseException Is thrown when Spring Boot is started. As you can see, this exception inherits from WebServerException, which inherits from RuntimeException. A member variable port is defined. When the system throws an exception, it must pass in the current port number. The final printed message will also carry the description of the port.

public class PortInUseException extends WebServerException {

	private final int port;

	/**
	 * Creates a new port in use exception for the given {@code port}.
	 * @param port the port that was in use
	 */
	public PortInUseException(int port) {
		this(port, null);
	}

	/**
	 * Creates a new port in use exception for the given {@code port}.
	 * @param port the port that was in use
	 * @param cause the cause of the exception
	 */
	public PortInUseException(int port, Throwable cause) {
		super("Port " + port + " is already in use", cause);
		this.port = port; }... }Copy the code

This custom exception class meets most of the requirements and is a great step to take when you’re actually developing your business code. But Spring Boot, as a framework, doesn’t stop there. It argues that errors reported within the framework should have more detailed analysis of errors and descriptions that guide user behavior.

One solution is to modify the custom exception class directly, but this behavior itself violates the open and close principle and is not flexible enough. It will also bind the exception to Spring Boot, which is not flexible enough.

Let’s look at the design of Spring Boot.

Spring Boot introduces the concept of the FailureAnalyzer, which converts error information into a more detailed error analysis report, and the FailureAnalysisReporter, which presents the report.

When Spring Boot catches a framework exception, such as PortInUseException, it uses the error analyzer to analyze it (not every error is analyzed; more on that later) and produces an error analysis report.

Spring Boot defines the FailureAnalyzer interface as follows. There is only one Analyze method. The parameter is Throwable, which is the base class of all exceptions, and returns a FailureAnalyzer, or error analysis report.

@FunctionalInterface
public interface FailureAnalyzer {
	FailureAnalysis analyze(Throwable failure);
}
Copy the code

The FailureAnalyzer needs to indicate which exception analyzer it is. AbstractFailureAnalyzer implements the FailureAnalyzer method and declares a generic class on the class that is the exception class of interest to the analyzer. The code is very simple. The core is to call the exception’s getCause() to loop/traverse to check the root cause of the exception and its message, and determine whether it is the same type as generics. Most parsers in Spring Boot inherit AbstractFailureAnalyzer.

public abstract class AbstractFailureAnalyzer<T extends Throwable> implements FailureAnalyzer {... }Copy the code

Looking back at the error analysis report, this class contains a detailed description of the error, how the error was resolved, and the cause of the exception itself. It can be considered that this report is the secondary encapsulation of the exception class by Srping Boot, adding more detailed exception information without destroying the original exception information.

public class FailureAnalysis {

	private final String description;

	private final String action;

	private final Throwable cause;

	public FailureAnalysis(String description, String action, Throwable cause) {
		this.description = description;
		this.action = action;
		this.cause = cause; }... }Copy the code

Next comes the FailureAnalysisReporter, which is responsible for displaying these error analysis reports. As can be seen, FailureAnalysisReporter is also a single-method interface, and the input is the error analysis report.

@FunctionalInterface
public interface FailureAnalysisReporter {
	void report(FailureAnalysis analysis);
}
Copy the code

Spring provided a FailureAnalysisReporter Boot default, that is LoggingFailureAnalysisReporter. Depending on the current log level, this class calls the debug or error methods of the log to print.

public final class LoggingFailureAnalysisReporter implements FailureAnalysisReporter {

	private static final Log logger = LogFactory.getLog(LoggingFailureAnalysisReporter.class);

	@Override
	public void report(FailureAnalysis failureAnalysis) {
		if (logger.isDebugEnabled()) {
			logger.debug("Application failed to start due to an exception", failureAnalysis.getCause());
		}
		if(logger.isErrorEnabled()) { logger.error(buildMessage(failureAnalysis)); }}... }Copy the code

This section describes the Spring Boot exception handling scheme. After Spring Boot captures an exception, it invokes the FailureAnalyzer corresponding to the exception to analyze it and convert the exception to FailureAnalysis. Then call FailureAnalysisReporter to print the exception analysis report.

Exception Handling Process

Now that you know how Spring Boot handles exceptions, let’s take a look at how the process is organized.

The first thing to know is that Spring Boot exception handling is for starting an exception in the Run method, and handleRunFailure in the Catch block is the starting point for exception handling.

public ConfigurableApplicationContext run(String... args) {...try{... }catch (Throwable ex) {
    handleRunFailure(context, ex, listeners);
    throw newIllegalStateException(ex); }... }Copy the code

HandleRunFailure does a couple of things.

  1. Determine the code for program exit. 0 indicates normal regression, and greater than 0 indicates abnormal exit.
  2. If there are listeners, the ApplicationFailedEvent event is emitted, indicating that the application fails to start.
  3. Get SpringBootExceptionReporter collection.
  4. Traverse SpringBootExceptionReporter collection, call each SpringBootExceptionReporter reportException method.
  5. Clean up the context

Let’s focus on steps 3 and 4. There appeared a new interface, SpringBootExceptionReporter. It encapsulates the process of taking multiple Fail Analyzers and analyzing exceptions.

SpringBootExceptionReporter itself is Spring loaded using SPI.

getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					newClass<? >[] { ConfigurableApplicationContext.class }, context);Copy the code

Spring Boot specifies that the implementation of this interface in Spring. factories is FailureAnalyzers, not to be confused with the previous FailureAnalyzers. The FailureAnalyzers can be thought of as managing the FailureAnalyzers.

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
Copy the code

The FailureAnalyzers load all FailureAnalyzers in the constructor. Also use SPI for loading.

FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
  this.classLoader = (classLoader ! =null)? classLoader : getClassLoader(context);this.analyzers = loadFailureAnalyzers(context, this.classLoader);
}
Copy the code

In Spring. factories, you can see which exceptions in Spring Boot have FailureAnalyzer, and which exceptions are repackaged by Spring Boot. Here is all FailureAnalyzer, some of them before we PortInUseFailureAnalyzer, for example, there are some exceptions, automatic injection failed to NoUniqueBeanDefinitionFailureAnalyzer, BindFailureAnalyzer, etc.

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.config.ConfigDataNotFoundFailureAnalyzer,\
org.springframework.boot.context.properties.IncompatibleConfigurationFailureAnalyzer,\
org.springframework.boot.context.properties.NotConstructorBoundInjectionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PatternParseFailureAnalyzer,\
org.springframework.boot.liquibase.LiquibaseChangelogMissingFailureAnalyzer
Copy the code

Then comes the most important reportFailure method. Spring the Boot after the initialization is completed all SpringBootExceptionReporter will call each instance reportException method in turn to handle exceptions, if the method returns to true, Don’t need to deal with the subsequent SpringBootExceptionReporter said the exception, return false to continue processing.

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); }}Copy the code

Because of Spring the Boot a default SpringBootExceptionReporter only, that is FailureAnalyzers, all will perform FailureAnalyzers reportException method.

@Override
public boolean reportException(Throwable failure) {
  // Get the error analysis report
  FailureAnalysis analysis = analyze(failure, this.analyzers);
  // Print the report
  return report(analysis, this.classLoader);
}
Copy the code

As we know, FailureAnalyzers load all FailureAnalyzers in SPI mode, and the Analyze method actually traverses the FailureAnalyzers set. Call the Analyze methods in turn until a FailureAnalysis or traversal is obtained. More specifically, each FailureAnalyzer determines whether the current exception is an exception it is interested in (via a generic declaration on the class), and if so, rewraps the exception, adding a description and an action.

In the report method, the implementation of FailureAnalysisReporter is loaded in SPI mode and its report method is called. So it’s the call is LoggingFailureAnalysisReporter report method.

# Failure Analysis Reporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
Copy the code

The essence of the whole process is to obtain the FailureAnalyzer analysis exception, and then through the FailureAnalysisReporter analysis results are displayed. In order to extend this process, Spring loads almost every part of the process using SPI.

After the exception is printed, the final step in the process is to close the current context by calling context.close(). In this method, the context is set to close, the ContextClosedEvent event is issued, the bean is cleaned up, the bean factory is cleaned up, and the context is closed. Delete the listener, and finally execute the JVM’s close hook method.

Extended ways to handle exceptions

Spring Boot handles framework exceptions with three SPI loads, or three extension points.

The first extension points are SpringBootExceptionReporter, Spring the default is to implement the Boot is FailureAnalyzers. Implementing this interface redefines the entire exception handling process.

The second extension point is FailureAnalyzer, which can rewrap an exception and return an exception analysis report.

The third extension point is the FailureAnalysisReporter, which allows you to customize how exceptions are presented, using the logging component by default.

These Spring Boot extension points are basically provided for internal extensions to the framework, and some deeply integrated Spring JARS may also extend exceptions. However, this does not mean that this exception handling method is not valuable. On the contrary, when we are in complex business scenarios or tool class platforms, we are likely to encounter unfriendly exception description and cannot be quickly repaired. In this case, we can try to refer to the practice of Spring Boot to abstract up a layer, so that we can get a better experience.

conclusion

  1. Spring Boot has designed an exception handling scheme to present errors during startup in a more friendly and flexible way.

  2. Spring Boot introduces the concept of the FailureAnalyzer, which converts error information into a more detailed error analysis report, and the FailureAnalysisReporter, which presents the report.

  3. The responsibility of the fault analyzer is to identify the current type of error, and to repackage the errors of interest. The result of that packaging is a FailureAnalysis report.

  4. In the FailureAnalysis report, in addition to the original error information, description and action are added to remind users of subsequent actions.

  5. In the Spring Boot framework exception handling system, SPI is widely used to load specific classes, which is convenient for the framework to expand the exception handling scheme, special exception checking and exception display.

If you think you’ve learned something, please give it a thumbs up!