Spring Boot 自定义 FailureAnalyzer

本文介绍 Spring Boot 2 自定义 FailureAnalyzer 的方法。


目录

  • FailureAnalyzer 简介
  • 开发环境
  • 基础示例
  • 自定义多个 FailureAnalyzer 示例
  • 总结

FailureAnalyzer 简介

org.springframework.boot.diagnostics.FailureAnalyzer 拦截启动时异常,将异常转换成更加易读的信息并包装成 org.springframework.boot.diagnostics.FailureAnalysis 对象。Spring Boot 为应用上下文相关异常、JSR-303 validations 提供了此类分析器,也可以自定义 FailureAnalyzer

org.springframework.boot.diagnostics.AbstractFailureAnalyzerFailureAnalyzer 的抽象实现,检查要处理的异常中是否存在指定的异常类型。扩展 AbstractFailureAnalyzer 可以实现自定义异常处理,如无法处理该异常则可以返回 null,以便其它 FailureAnalyzer 有机会处理该异常。


开发环境

  • Oracle JDK 11.0.6
  • Apache Maven 3.6.3
  • IntelliJ IDEA (Version 2019.3.2)

基础示例

  1. 创建 Spring Boot 工程,参考:IntelliJ IDEA 创建 Spring Boot 工程。

  2. 生成的 pom 文件如下,不做任何修改



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.4.RELEASE
        
    
    tutorial.spring.boot
    spring-boot-failure-analyzer
    0.0.1-SNAPSHOT
    spring-boot-failure-analyzer
    自定义FailureAnalyzer

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


  1. 不添加任何配置(不修改 application.properties 或 application.yml 文件),定义一个配置类,使用 @Value 注解注入一个未定义的属性。
package tutorial.spring.boot.failure.analyzer.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InitialConfig {

    @Value("${application.name}")
    private String applicationName;

}
  1. 启动应用,日志打印出以下错误
2019-04-20 13:37:13.245  INFO 3920 --- [           main] f.a.SpringBootFailureAnalyzerApplication : Starting SpringBootFailureAnalyzerApplication on ... with PID 3920 (D:\Tutorial\spring-boot-tutorial\spring-boot-failure-analyzer\target\classes started by ... in D:\Tutorial\spring-boot-tutorial)
2019-04-20 13:37:13.248  INFO 3920 --- [           main] f.a.SpringBootFailureAnalyzerApplication : No active profile set, falling back to default profiles: default
2019-04-20 13:37:13.742  WARN 3920 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initialConfig': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'application.name' in value "${application.name}"
2019-04-20 13:37:13.750  INFO 3920 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-04-20 13:37:13.765 ERROR 3920 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initialConfig': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'application.name' in value "${application.name}"
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:380) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at tutorial.spring.boot.failure.analyzer.SpringBootFailureAnalyzerApplication.main(SpringBootFailureAnalyzerApplication.java:10) [classes/:na]
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'application.name' in value "${application.name}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172) ~[spring-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124) ~[spring-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:237) ~[spring-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:211) ~[spring-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:851) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1188) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    ... 16 common frames omitted
  1. 自定义 FailureAnalyzer,处理 java.lang.IllegalArgumentException 异常。

5.1 首先,创建自定义 FailureAnalyzer 类,继承 AbstractFailureAnalyzer

package tutorial.spring.boot.failure.analyzer.custom;

import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;

public class IllegalArgumentFailureAnalyzer extends AbstractFailureAnalyzer {

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, IllegalArgumentException cause) {
        return new FailureAnalysis(cause.getMessage(), "Please check startup argument!", cause);
    }
}

5.2 其次,在 src/main/resources 路径下新建 META-INF/spring.factories,在 spring.factories 文件中添加一下内容。

org.springframework.boot.diagnostics.FailureAnalyzer=tutorial.spring.boot.failure.analyzer.custom.IllegalArgumentFailureAnalyzer

这一步非常重要,自定义的 FailureAnalyzer 实现必须通过这种方式进行注册!

  1. 再次运行应用,启动日志如下。
2019-04-20 13:43:52.099  INFO 4708 --- [           main] f.a.SpringBootFailureAnalyzerApplication : Starting SpringBootFailureAnalyzerApplication on ... with PID 4708 (D:\Tutorial\spring-boot-tutorial\spring-boot-failure-analyzer\target\classes started by ... in D:\Tutorial\spring-boot-tutorial)
2019-04-20 13:43:52.102  INFO 4708 --- [           main] f.a.SpringBootFailureAnalyzerApplication : No active profile set, falling back to default profiles: default
2019-04-20 13:43:52.631  WARN 4708 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initialConfig': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'application.name' in value "${application.name}"
2019-04-20 13:43:52.639  INFO 4708 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-04-20 13:43:52.641 ERROR 4708 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Could not resolve placeholder 'application.name' in value "${application.name}"

Action:

Please check startup argument!

自定义多个 FailureAnalyzer 示例

  1. 在以上示例基础上新增一个 NullPointerFailureAnalyzer,处理 java.lang.NullPointerException 异常。
package tutorial.spring.boot.failure.analyzer.custom;

import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;

public class NullPointerFailureAnalyzer extends AbstractFailureAnalyzer {

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, NullPointerException cause) {
        return new FailureAnalysis(cause.getMessage(), "Please check null pointer!", cause);
    }
}
  1. 修改 InitialConfig,创造一个 NullPointerException 出现场景。
package tutorial.spring.boot.failure.analyzer.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class InitialConfig {

    public InitialConfig() {
        String param = null;
        System.out.println("Param: " + param.toString());
    }
}
  1. 修改 src/main/resources/META-INF/spring.factories,注册 NullPointerFailureAnalyzer
org.springframework.boot.diagnostics.FailureAnalyzer=\
tutorial.spring.boot.failure.analyzer.custom.IllegalArgumentFailureAnalyzer,\
tutorial.spring.boot.failure.analyzer.custom.NullPointerFailureAnalyzer
  1. 运行应用,启动日志如下。
2019-04-20 13:55:40.124  INFO 11564 --- [           main] f.a.SpringBootFailureAnalyzerApplication : Starting SpringBootFailureAnalyzerApplication on ... with PID 11564 (D:\Tutorial\spring-boot-tutorial\spring-boot-failure-analyzer\target\classes started by ... in D:\Tutorial\spring-boot-tutorial)
2019-04-20 13:55:40.129  INFO 11564 --- [           main] f.a.SpringBootFailureAnalyzerApplication : No active profile set, falling back to default profiles: default
2019-04-20 13:55:40.684  WARN 11564 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initialConfig' defined in file [D:\Tutorial\spring-boot-tutorial\spring-boot-failure-analyzer\target\classes\tutorial\spring\boot\failure\analyzer\config\InitialConfig.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [tutorial.spring.boot.failure.analyzer.config.InitialConfig$$EnhancerBySpringCGLIB$$6a84093d]: Constructor threw exception; nested exception is java.lang.NullPointerException
2019-04-20 13:55:40.693  INFO 11564 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-04-20 13:55:40.694 ERROR 11564 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

null

Action:

Please check null pointer!

总结

自定义 FailureAnalyzer 对于监控应用启动过程十分有用。
注意:自定义 FailureAnalyzer 处理不了应用启动 main 方法中的异常,可以将以上示例中 NullPointerException 出现场景放在启动的 main 方法中,如下所示。

package tutorial.spring.boot.failure.analyzer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootFailureAnalyzerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootFailureAnalyzerApplication.class, args);
        String param = null;
        System.out.println("Param: " + param.toString());
    }

}

启动日志如下。

2019-04-20 13:58:55.885  INFO 5208 --- [           main] f.a.SpringBootFailureAnalyzerApplication : Starting SpringBootFailureAnalyzerApplication on ... with PID 5208 (D:\Tutorial\spring-boot-tutorial\spring-boot-failure-analyzer\target\classes started by ... in D:\Tutorial\spring-boot-tutorial)
2019-04-20 13:58:55.889  INFO 5208 --- [           main] f.a.SpringBootFailureAnalyzerApplication : No active profile set, falling back to default profiles: default
Exception in thread "main" java.lang.NullPointerException
    at tutorial.spring.boot.failure.analyzer.SpringBootFailureAnalyzerApplication.main(SpringBootFailureAnalyzerApplication.java:12)
2019-04-20 13:58:56.562  INFO 5208 --- [           main] f.a.SpringBootFailureAnalyzerApplication : Started SpringBootFailureAnalyzerApplication in 1.07 seconds (JVM running for 1.802)

源码:https://gitee.com/jyl1626938665/spring-tutorial/tree/master/spring-boot-tutorial/spring-boot-failure-analyzer

你可能感兴趣的:(Spring Boot 自定义 FailureAnalyzer)