Failure Analysis 就是:SpringBoot 启动错误时,日志会打印专业的错误信息和解决该问题的具体措施。
举个例子
当重复启动 SpringBoot 会输出以下信息:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2020-04-11 14:20:06.773 ERROR 1899 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter : *************************** 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.
一、从日志类剖析
日志输出类是`o.s.b.d.LoggingFailureAnalysisReporter`,内容有错误描述`Description`和解决措施`Action`。
LoggingFailureAnalysisReporter
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)); } } 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(); } }
LoggingFailureAnalysisReporter 是一个 实现 FailureAnalysisReporter 接口的 final 类。
FailureAnalysisReporter
@FunctionalInterface public interface FailureAnalysisReporter { void report(FailureAnalysis analysis); }
`@FunctionalInterface` 注解修饰的功能性接口只提供一个 `void report(FailureAnalysis analysis)` 方法,向开发人员输出被传入的 analysis 对象。
FailureAnalysis
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; } public String getDescription() { return this.description; } public String getAction() { return this.action; } public Throwable getCause() { return this.cause; } }
一个简单的失败分析对象,包含三个属性:
二、从 Spring 加载剖析
SpringFactoriesLoader
public final class SpringFactoriesLoader { public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>(); private SpringFactoriesLoader() { } public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) { Assert.notNull(factoryType, "'factoryType' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames); } List<T> result = new ArrayList<>(factoryImplementationNames.size()); for (String factoryImplementationName : factoryImplementationNames) { result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; } public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } @SuppressWarnings("unchecked") private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) { try { Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader); if (!factoryType.isAssignableFrom(factoryImplementationClass)) { throw new IllegalArgumentException( "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]"); } return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException( "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", ex); } } }
SpringFactoriesLoader 是 Spring 框架内部使用的通用工厂加载机制。
loadFactories 方法
loadFactoryNames 方法
loadSpringFactories 方法
instantiateFactory 方法
spring.factories
`spring-boot-2.2.6.RELEASE.jar` 源码包中,包含了一个 `spring.factories` 文件。
# Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ 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 # FailureAnalysisReporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
Properties 格式的配置文件。等号前面是接口类全路径,等号后面是接口实现类的全路径。
SpringFactoriesLoader 类在加载工厂类时,会将 factories 文件中的实现类进行实例化。
总结
Failure Analysis,是 Spring 启动类预先准备好的错误说明、解决措施。Spring 启动时,会通过 SpringFactoriesLoader 加载 spring.factories 文件中配置的类路径。当 Spring 启动报错时,会调用日志输出接口。该接口会返回具体类的重写接口的结果,并写入日志文件。
最后,下周二继续努力。
本文分享自微信公众号 - FoamValue(gh_3c635269f459),作者:FoamValue
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2020-04-14
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句