1, Custom error analyzer
First 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 { //An ArithmeticException exception will be thrown here int i = 1/0; } }
If you run it directly, the error message is just java stack information.
These java standard stack information can be used for troubleshooting errors. However, SpringBoot hopes to give some modification suggestions after reporting an error to teach you to do the "right thing". At this point, you can customize an error analyzer. The error analyzer contains three parts: description, action and cause. In fact, the action to be added is the recommended action.
public class ArithmeticFailureAnalyzer extends AbstractFailureAnalyzer<ArithmeticException> { @Override protected FailureAnalysis analyze(Throwable rootFailure, ArithmeticException cause) { return new FailureAnalysis(cause.getMessage(), "Calculate Error.", cause); } }
Then configure it to spring In factories:
org.springframework.boot.diagnostics.FailureAnalyzer=\ com.roy.failureAnalyzer.ArithmeticFailureAnalyzer
The error prompt becomes a more friendly way:
This error prompt adds a detailed description and can give corresponding handling suggestions for the error.
In fact, how to handle all kinds of exceptions gracefully is a very important link to improve the robustness of applications. This link, in daily development, is easy to be ignored by most programmers. For SpringBoot, many people are just eager to build applications in the learning process. In today's Internet environment, Spring may still be bombarded by interviews with many large companies. More and more people go back to in-depth research and experience the elegance. But for SpringBoot, there is little corresponding enthusiasm. In fact, for application development, SpringBoot system can provide only more help than Spring, no less, because SpringBoot is naturally closer to the application. This neglect is a pity.
2, Interpretation of core mechanism
The interpretation of SpringBoot exception handling mechanism starts with the run method of SpringApplication. In fact, the processing link of the source code is not very long, so it is 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; }
Trace down from the handleRunFailure method
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, SpringApplicationRunListeners listeners) { try { try { //Handling exception information handleExitCode(context, exception); //Broadcast abnormal events if (listeners != null) { listeners.failed(context, exception); } } finally { //< = = our focus, exception report reportFailure(getExceptionReporters(context), exception); if (context != null) { context.close(); } } } catch (Exception ex) { logger.warn("Unable to close ApplicationContext", ex); } ReflectionUtils.rethrowRuntimeException(exception); }
First look at the getExceptionReporters method
private Collection<SpringBootExceptionReporter> getExceptionReporters(ConfigurableApplicationContext context) { try { return getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class<?>[] { ConfigurableApplicationContext.class }, context); } catch (Throwable ex) { return Collections.emptyList(); } } private <T> Collection<T> getSpringFactoriesInstances(Class<T> 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; }
Here we see the most familiar code in this article, spring factoriesloader. Here is to load spring Configuration of exception handlers in factories:
# spring-boot-2.4.5.jar # Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers
So the whole getExceptionReporters method returns an instance of FailureAnalyzers. Once you know this, it's very clear to trace down the code.
After the exception handler is loaded, the exception report will be printed through its reportException method.
#SpringApplication.java private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) { try { //< = = = print exception report. In the defau lt configuration, there is only one implementation of FailureAnalyzers, so it can be extended here. for (SpringBootExceptionReporter reporter : exceptionReporters) { if (reporter.reportException(failure)) {//< = = = print error log 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); } }
Therefore, the next logic needs to go to org springframework. boot. diagnostics. See his reportException method in failureanalyzers.
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) { return analysis; } } 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; } }
The core report method has only two lines of code, which is very easy to understand. The analyze method will analyze spring Org.org configured in the factories file springframework. boot. diagnostics. FailureAnalyzer implements the class, then calls the corresponding component's analyze method to return a FailureAnalysis object.
Then, when the following report method prints the log, it will also parse spring Org. In factories springframework. boot. diagnostics. Failureanalysisreporter component, obtain the specific implementation class, and then print the log information to the Logger.
# spring-boot-2.4.5.jar / spring.factories # Failure Analysis Reporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
The final printing method is also quite simple and rough:
#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(); }
**Key points: * * understanding this mechanism also means that there can be a very clear extension mechanism for the operation monitoring of SpringBoot applications. The analysis of abnormal conditions during operation is an important reference to improve the robustness of applications in many business scenarios. And spring The failure analysis reporter mechanism in factories is a very good and convenient extension point. For example, using this mechanism, you can easily record the exception information in the application to redis or hadoop big data components for subsequent batch statistical calculation.
In fact, when you want to understand the source code of the spring boot starter actuator component, it will be expanded in spring See a familiar configuration information in factories. Can you have a different understanding of this component?
org.springframework.boot.diagnostics.FailureAnalyzer=\org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer
3, The core implementation of SpringBoot
Next, let's summarize the implementation 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
This is the error prompt customized for various abnormal situations, which is very important for building 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); } }
I believe that the error prompt of this port occupation should be seen by everyone.