Spring Boot 基础(五)FailureAnalyzer

1,230 阅读3分钟

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

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

一、自定义 FailureAnalyzer

  1. 定义一个配置类,使用@Value注入一个未定义的属性。
package com.example.demo;

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

@Configuration
public class Config {

    @Value("${undefined}")
    private String value;
}
  1. 启动日志打印错误提示
2021-12-06 20:48:27.531  INFO 4088 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 17.0.1 on DESKTOP with PID 4088 (D:\learn\Spring\demo\target\classes started by ... in d:\learn\Spring\demo)
2021-12-06 20:48:27.534  INFO 4088 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2021-12-06 20:48:27.916  WARN 4088 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'config': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'undefined' in value "${undefined}"
2021-12-06 20:48:27.924  INFO 4088 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-12-06 20:48:27.942 ERROR 4088 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'config': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'undefined' in value "${undefined}"
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:405) ~[spring-beans-5.3.13.jar:5.3.13]
AutowiredAnnotationBeanPostProcessor.java:405
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431) ~[spring-beans-5.3.13.jar:5.3.13]
AbstractAutowireCapableBeanFactory.java:1431
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.13.jar:5.3.13]
AbstractAutowireCapableBeanFactory.java:619
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.13.jar:5.3.13]
AbstractAutowireCapableBeanFactory.java:542
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.13.jar:5.3.13]
AbstractBeanFactory.java:335
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.13.jar:5.3.13]
DefaultSingletonBeanRegistry.java:234
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.13.jar:5.3.13]
AbstractBeanFactory.java:333
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.13.jar:5.3.13]
AbstractBeanFactory.java:208
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.13.jar:5.3.13]
DefaultListableBeanFactory.java:944
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.13.jar:5.3.13]
AbstractApplicationContext.java:918
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.13.jar:5.3.13]
		at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290) ~[spring-boot-2.6.1.jar:2.6.1]
SpringApplication.java:1290
	at com.example.demo.DemoApplication.main(DemoApplication.java:11) ~[classes/:na]
DemoApplication.java:11
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'undefined' in value "${undefined}"
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180) ~[spring-core-5.3.13.jar:5.3.13]
PropertyPlaceholderHelper.java:180
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-5.3.13.jar:5.3.13]
PropertyPlaceholderHelper.java:126
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) ~[spring-core-5.3.13.jar:5.3.13]
AbstractPropertyResolver.java:239
	at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) ~[spring-core-5.3.13.jar:5.3.13]
AbstractPropertyResolver.java:210
	at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175) ~[spring-context-5.3.13.jar:5.3.13]
PropertySourcesPlaceholderConfigurer.java:175
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:936) ~[spring-beans-5.3.13.jar:5.3.13]
AbstractBeanFactory.java:936
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321) ~[spring-beans-5.3.13.jar:5.3.13]
DefaultListableBeanFactory.java:1321
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.13.jar:5.3.13]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:656) ~[spring-beans-5.3.13.jar:5.3.13]
AutowiredAnnotationBeanPostProcessor.java:656
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639) ~[spring-beans-5.3.13.jar:5.3.13]
AutowiredAnnotationBeanPostProcessor.java:639
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.13.jar:5.3.13]
InjectionMetadata.java:119
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.13.jar:5.3.13]
AutowiredAnnotationBeanPostProcessor.java:399
	... 16 common frames omitted

  1. 自定义FailureAnalyzer继承AbstractFailureAnalyzer
package com.example.demo;

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

public class IllegalInjectionFailureAnalyzer extends AbstractFailureAnalyzer<IllegalArgumentException> {

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, IllegalArgumentException cause) {
        return new FailureAnalysis(cause.getMessage(), "Please check injection param!", cause);
    }
}
  1. src/main/resource目录下新建META-INF/spring.factories,在spring.factories中添加如下内容,这是注册自定义FailureAnalyzer的方式。
org.springframework.boot.diagnostics.FailureAnalyzer=com.example.demo.IllegalInjectionFailureAnalyzer
  1. 再次启动应用,失败日志如下。
2021-12-07 10:50:39.875  INFO 22236 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 17.0.1 on DESKTOP with PID 22236 (D:\learn\Spring\demo\target\classes started by ... in d:\learn\Spring\demo)
2021-12-07 10:50:39.877  INFO 22236 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2021-12-07 10:50:40.241  WARN 22236 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'config': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'undefined' in value "${undefined}"
2021-12-07 10:50:40.250  INFO 22236 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-12-07 10:50:40.270 ERROR 22236 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

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

Description:

Could not resolve placeholder 'undefined' in value "${undefined}"

Action:

Please check injection param!

二、自定义多个 FailureAnalyzer

  1. 在上例基础上新增一个自定义FailureAnalyzer类处理空指针异常。
package com.example.demo;

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

public class NullPointerFailureAnalyzer extends AbstractFailureAnalyzer<NullPointerException> {

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, NullPointerException cause) {
        return new FailureAnalysis(cause.getMessage(), "Please check null pointer!", cause);
    }
}
  1. 修改src/main/resources/META-INF/spring.factories,注册NullPointerFailureAnalyzer
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.demo.IllegalInjectionFailureAnalyzer,\
com.example.demo.NullPointerFailureAnalyzer
  1. 修改配置类,模拟空指针异常。
package com.example.demo;

import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    public Config() {
        String param = null;
        System.out.println("Param: " + param.toString());
    }
}
  1. 启动应用,错误日志如下。

2021-12-07 16:58:54.949  INFO 8716 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 17.0.1 on DESKTOP with PID 8716 (D:\learn\Spring\demo\target\classes started by ... in d:\learn\Spring\demo)
2021-12-07 16:58:54.951  INFO 8716 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2021-12-07 16:58:55.300  WARN 8716 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'config' defined in file [D:\learn\Spring\demo\target\classes\com\example\demo\Config.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.demo.Config$$EnhancerBySpringCGLIB$$47133ca1]: Constructor threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "String.toString()" because "param" is null
2021-12-07 16:58:55.310  INFO 8716 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-12-07 16:58:55.326 ERROR 8716 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

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

Description:

Cannot invoke "String.toString()" because "param" is null

Action:

Please check null pointer!

三、注意事项

自定义FailureAnalyzer对于监控应用的启动过程十分有用,但是自定义FailureAnalyzer处理不了main方法中的异常