一、FailureAnalyzer是什么
是SpringBoot提供给的功能启动失败分析器!即将启动失败的Exception捕获后,转化为容易理解的信息,比如将不能访问某个地址(比如数据库、Redis)转化为"无法访问地址"等提示信息!
二、源代码及案例分析
2.1FailureAnalysis源码介绍
IDEA下通过【Ctrl + shift + n】快捷键找到FailureAnalyzer源码,发现此接口只有一个方法,即将Throwable(发生了哪种异常)转换为FailureAnalysis(此异常描述)。
packageorg.springframework.boot.diagnostics; publicinterfaceFailureAnalyzer { FailureAnalysisanalyze(Throwablefailure); }
packageorg.springframework.boot.diagnostics; publicclassFailureAnalysis { //异常描述privatefinalStringdescription; //执行的操作privatefinalStringaction; //异常的原因privatefinalThrowablecause; publicFailureAnalysis(Stringdescription, Stringaction, Throwablecause) { this.description=description; this.action=action; this.cause=cause; } //...省略get方法}
2.2FailureAnalysis开源实现类介绍
通过查找FailureAnalyzer【alt+f7】找到其实现类
如上图,可以看到在META-INF下有很多SpringBoot出厂就定义好的【启动失败分析器】,这里我们选取RedisUrlSyntaxFailureAnalyzer这个来看看,如下。
建议先看第二个代码片段(AbstractFailureAnalyzer),在回过头看如下片段。这里这样排版是为了展示查找的逻辑,也就是寻根问底模式!
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//packageorg.springframework.boot.autoconfigure.data.redis; importjava.net.URI; importjava.net.URISyntaxException; importorg.springframework.boot.diagnostics.AbstractFailureAnalyzer; importorg.springframework.boot.diagnostics.FailureAnalysis; /**可以看到这里的泛型为RedisUrlSyntaxException*/classRedisUrlSyntaxFailureAnalyzerextendsAbstractFailureAnalyzer<RedisUrlSyntaxException> { RedisUrlSyntaxFailureAnalyzer() { } /*** 具体的FailureAnalysis生成操作。这里我们可以看到,Redis启动失败分析器分为了三种情况!*/protectedFailureAnalysisanalyze(ThrowablerootFailure, RedisUrlSyntaxExceptioncause) { try { URIuri=newURI(cause.getUrl()); if ("redis-sentinel".equals(uri.getScheme())) { returnnewFailureAnalysis(this.getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()), "Use spring.redis.sentinel properties instead of spring.redis.url to configure Redis sentinel addresses.", cause); } if ("redis-socket".equals(uri.getScheme())) { returnnewFailureAnalysis(this.getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()), "Configure the appropriate Spring Data Redis connection beans directly instead of setting the property 'spring.redis.url'.", cause); } if (!"redis".equals(uri.getScheme()) &&!"rediss".equals(uri.getScheme())) { returnnewFailureAnalysis(this.getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()), "Use the scheme 'redis://` for insecure or `rediss://` for secure Redis standalone configuration.", cause); } } catch (URISyntaxExceptionvar4) { } returnnewFailureAnalysis(this.getDefaultDescription(cause.getUrl()), "Review the value of the property 'spring.redis.url'.", cause); } /**这里就是启动时如果失败,给出的友好提示信息语句*/privateStringgetDefaultDescription(Stringurl) { return"The URL '"+url+"' is not valid for configuring Spring Data Redis. "; } privateStringgetUnsupportedSchemeDescription(Stringurl, Stringscheme) { returnthis.getDefaultDescription(url) +"The scheme '"+scheme+"' is not supported."; } }
发现此类继承了abstract class AbstractFailureAnalyzer,那么继续刨,详细说明看代码注释。
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//packageorg.springframework.boot.diagnostics; importorg.springframework.core.ResolvableType; publicabstractclassAbstractFailureAnalyzer<TextendsThrowable>implementsFailureAnalyzer { publicAbstractFailureAnalyzer() { } /*** 1.找到泛型异常对象T2.将具体操作留给子类**/publicFailureAnalysisanalyze(Throwablefailure) { Tcause=this.findCause(failure, this.getCauseType()); returncause!=null?this.analyze(failure, cause) : null; } /** 模板(钩子)方法,将具体的操作留给子类*/protectedabstractFailureAnalysisanalyze(ThrowablerootFailure, Tcause); /*** 获取AbstractFailureAnalyzer的类类型*/protectedClass<?extendsT>getCauseType() { returnResolvableType.forClass(AbstractFailureAnalyzer.class, this.getClass()).resolveGeneric(newint[0]); } /*** 如果是AbstractFailureAnalyzer类型,则直接返回,否则直接返回Throwable这里可以理解发生的异常处理类(即我们自己实现的FailureAnalyzer)是否继承了AbstractFailureAnalyzer*/protectedfinal<EextendsThrowable>EfindCause(Throwablefailure, Class<E>type) { while(failure!=null) { if (type.isInstance(failure)) { returnfailure; } failure=failure.getCause(); } returnnull; } }
在回过头来看RedisUrlSyntaxFailureAnalyzer上RedisUrlSyntaxException,这就是自定义的异常!
packageorg.springframework.boot.autoconfigure.data.redis; classRedisUrlSyntaxExceptionextendsRuntimeException { /**自定义异常属性Redis启动时候就需要连接某个地址,所以这里需要url这个属性。自己业务有需要也可以增加属性*/privatefinalStringurl; RedisUrlSyntaxException(Stringurl, Exceptioncause) { super(buildMessage(url), cause); this.url=url; } RedisUrlSyntaxException(Stringurl) { super(buildMessage(url)); this.url=url; } StringgetUrl() { returnthis.url; } privatestaticStringbuildMessage(Stringurl) { return"Invalid Redis URL '"+url+"'"; } }
三、自定义启动失败分析器
根据上面的分析可知,要想自定义一个启动失败分析器,需要两个类(自定义异常类、失败分析器)、一个配置文件(spring.factories)。开干!
3.1自定义异常类
packagecom.tab343.myspringboot.analyzer; /*** @Description : 自定义启动分析异常*/publicclassMyFailureAnalyzerExceptionextendsRuntimeException{ /**自定义异常属性,这里我只增加了name,读者按需增加!*/privatefinalStringname; publicMyFailureAnalyzerException(Stringname, Exceptioncause) { super(buildMessage(name), cause); this.name=name; } publicMyFailureAnalyzerException(Stringname) { super(buildMessage(name)); this.name=name; } publicStringgetName() { returnname; } privatestaticStringbuildMessage(Stringname) { return"发生了 【"+name+"】错误。"; } }
3.2启动错误分析器
packagecom.tab343.myspringboot.analyzer; importorg.springframework.boot.diagnostics.AbstractFailureAnalyzer; importorg.springframework.boot.diagnostics.FailureAnalysis; /*** 自定义启动失败分析器*/publicclassMyFailureAnalyzerextendsAbstractFailureAnalyzer<MyFailureAnalyzerException> { protectedFailureAnalysisanalyze(ThrowablerootFailure, MyFailureAnalyzerExceptioncause) { returnnewFailureAnalysis(cause.getName(), "看一哈,出错了喂!", cause); } }
3.3配置文件
在resources目录下创建META-INF/spring.factories文件,内容如下。
org.springframework.boot.diagnostics.FailureAnalyzer=\ com.tab343.myspringboot.analyzer.MyFailureAnalyzer
四、效果
通过自定义CommandLineRunner实现程序启动就抛出MyFailureAnalyzerException异常,达到启动就失败的场景!
packagecom.tab343.myspringboot.runner; importcom.tab343.myspringboot.analyzer.MyFailureAnalyzerException; importcom.tab343.myspringboot.mail.SimpleMail; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.boot.CommandLineRunner; importorg.springframework.core.annotation.Order; importorg.springframework.stereotype.Component; /*** 类描述:** @author sevensun* @version 1.0* @date 2020/10/27 下午8:35*/100) (publicclassMyCommandLineRunnerimplementsCommandLineRunner { privatestaticfinalLoggerlogger=LoggerFactory.getLogger(MyCommandLineRunner.class); privateSimpleMailsimpleMail; publicvoidrun(String... args) throwsException { logger.debug("自定义CommandLineRunner"); thrownewMyFailureAnalyzerException("自定义错误分析器!"); } }
完结,