@[toc]

Custom error parsers

First we throw an exception in the main method:

@SpringBootApplication
public class P1Application implements CommandLineRunner {

    public static void main(String[] args) {
        final SpringApplication application = new SpringApplication(P1Application.class);
        application.run(args);
    }

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void run(String... args) throws Exception {
        // It throws a ArithmeticException
        int i = 1/0; }}Copy the code

If run directly, the error message is just Java stack information.

This Java standard stack information is useful for troubleshooting errors. But SpringBoot is more likely to be able to report errors and suggest changes to do the “right thing.” At this point, you can customize an error parser. The error analyzer contains three parts: description, action, and cause. In fact, it usually needs to add the action, which is the recommended operation.

public class ArithmeticFailureAnalyzer extends AbstractFailureAnalyzer<ArithmeticException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, ArithmeticException cause) {
        return new FailureAnalysis(cause.getMessage(), "Calculate Error.", cause); }}Copy the code

Then configure it into spring.Factories:

org.springframework.boot.diagnostics.FailureAnalyzer=\
com.roy.failureAnalyzer.ArithmeticFailureAnalyzer
Copy the code

The error message becomes this more friendly way:

This error message adds a detailed description of the error, and can give corresponding handling suggestions for the error.

In fact, how to gracefully handle all kinds of exceptions when the program is running is a very important link to improve the robustness of the application. And this link, in the daily development, is very easy to be ignored by the vast majority of programmers. With SpringBoot, many people are just eager to get started and build applications during the learning process. In today’s Internet environment, where Spring may still be bombarded with interviews from large companies, more and more people are going back to delve into it and experience its elegance. But there is little enthusiasm for SpringBoot. In fact, for application development, SpringBoot system can provide more help than Spring, not less, because SpringBoot is inherently more close to the application. And this kind of neglect, cannot but say is a kind of regret.

Second, core mechanism interpretation

The interpretation of the SpringBoot exception handling mechanism starts with the Run method of the SpringApplication. Source code processing link is not actually very long, directly on the key code.

#SpringApplication
public ConfigurableApplicationContext run(String... args) {...try{... }catch (Throwable ex) {
			handleRunFailure(context, ex, listeners); //<=== = exception mechanism entry
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null); //<=== = exception mechanism entry
			throw new IllegalStateException(ex);
		}
		return context;
	}
Copy the code

Trace down from the handleRunFailure method

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, SpringApplicationRunListeners listeners) {
		try {
			try {
                // Handle exception information
				handleExitCode(context, exception);
                // Broadcast an exception event
				if(listeners ! =null) { listeners.failed(context, exception); }}finally {
                //<== our focus, exception reporting
				reportFailure(getExceptionReporters(context), exception);
				if(context ! =null) { context.close(); }}}catch (Exception ex) {
			logger.warn("Unable to close ApplicationContext", ex);
		}
		ReflectionUtils.rethrowRuntimeException(exception);
	}
Copy the code

First look at the getExceptionReporters method

private Collection<SpringBootExceptionReporter> getExceptionReporters(ConfigurableApplicationContext context) {
		try {
			return getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					newClass<? >[] { ConfigurableApplicationContext.class }, context); }catch (Throwable ex) {
			returnCollections.emptyList(); }}private <T> Collection<T> getSpringFactoriesInstances(Class
       
         type, Class
        [] parameterTypes, Object... args)
        {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
    	// Familiar code
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
Copy the code

Here you’ll see the code we’re most familiar with in this article, the SpringFactoriesLoader. Here is loading the configuration of the exception handler in Spring. factories:

# spring - the boot - 2.4.5. Jar
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
Copy the code

So the entire getExceptionReporters method returns an instance of FailureAnalyzers. With this in mind, it becomes clear to trace the code further down.

Once the exception handler is loaded, an exception report is printed through its reportException method.

#SpringApplication.java
private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
		try {
            //<=== Prints an exception report. There is only one implementation of FailureAnalyzers in the default configuration, so this is actually extensible.
			for (SpringBootExceptionReporter reporter : exceptionReporters) {
				if (reporter.reportException(failure)) {//<=== Prints error logs
					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

So, the next logical requires to org. Springframework. Boot. Diagnostics. FailureAnalyzers to check his reportException method.

final class FailureAnalyzers implements SpringBootExceptionReporter {...@Override
	public boolean reportException(Throwable failure) {
		FailureAnalysis analysis = analyze(failure, this.analyzers);
		return report(analysis, this.classLoader);
	}
    
    private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {
		for (FailureAnalyzer analyzer : analyzers) {
			try {
				FailureAnalysis analysis = analyzer.analyze(failure);
				if(analysis ! =null) {
					returnanalysis; }}catch (Throwable ex) {
				logger.trace(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex); }}return null;
	}

	private boolean report(FailureAnalysis analysis, ClassLoader classLoader) {
		List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(FailureAnalysisReporter.class,
				classLoader);
		if (analysis == null || reporters.isEmpty()) {
			return false;
		}
		for (FailureAnalysisReporter reporter : reporters) {
			reporter.report(analysis);
		}
		return true; }}Copy the code

The core report method is just two lines of code, and it’s pretty easy to understand. Analyze method will Spring. Factories file configured in org. Springframework. Boot. Diagnostics. FailureAnalyzer implementation class, and then call the corresponding component analyze method, Returns a FailureAnalysis object.

Method and then the following report print log process, also by parsing spring. The factories in the org. Springframework. Boot. Diagnostics. FailureAnalysisReporter components, obtain the specific implementation class, The log information is then printed to Logger.

# spring-boot-2.4. 5.jar / spring.factories
# Failure Analysis Reporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
Copy the code

The final method of printing is also quite simple and crude:

#LoggingFailureAnalysisReporter
	private String buildMessage(FailureAnalysis failureAnalysis) {
		StringBuilder builder = new StringBuilder();
		builder.append(String.format("%n%n"));
		builder.append(String.format("***************************%n"));
		builder.append(String.format("APPLICATION FAILED TO START%n"));
		builder.append(String.format("***************************%n%n"));
		builder.append(String.format("Description:%n%n"));
		builder.append(String.format("%s%n", failureAnalysis.getDescription()));
		if (StringUtils.hasText(failureAnalysis.getAction())) {
			builder.append(String.format("%nAction:%n%n"));
			builder.append(String.format("%s%n", failureAnalysis.getAction()));
		}
		return builder.toString();
	}
Copy the code

** Important: ** Knowing this mechanism also means that there is a very clear extension mechanism for monitoring the health of SpringBoot applications. Exception analysis during operation is an important reference for improving application robustness in many business scenarios. The FailureAnalysisReporter mechanism in Spring.facotries is a very good and convenient extension point. For example, by using this mechanism, exception information in applications can be easily recorded to Redis or Hadoop big data components for subsequent batch statistical calculation.

In fact, by extension, when you try to understand the source of the Spring-boot-starter -actuator component, you’ll see a familiar configuration in Spring.Factories. Is it possible to interpret this component differently?

org.springframework.boot.diagnostics.FailureAnalyzer=\org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer
Copy the code

Three, the core implementation of SpringBoot

Here’s a summary of the implementations provided by default in SpringBoot:

#spirng-boot.jar
# 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
# Failure Analysis Reporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter


# spring-boot-autoconfigure
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
Copy the code

This is a custom error prompt for various exceptions, which is important to build a robust application. For example PortInUseFailureAnalyzer

class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
	@Override
	protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
		return new FailureAnalysis("Web server failed to start. Port " + cause.getPort() + " was already in use."."Identify and stop the process that's listening on port " + cause.getPort() + " or configure this "
						+ "application to listen on another port.", cause); }}Copy the code

Believe this port occupation error prompt everyone should have seen the.