SpringBoot之"How to"系列(一)---自定义FailureAnalyzer

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: FailureAnalyzer是什么、自定义FailureAnalyzer如何实现

一、FailureAnalyzer是什么

是SpringBoot提供给的功能启动失败分析器!即将启动失败的Exception捕获后,转化为容易理解的信息,比如将不能访问某个地址(比如数据库、Redis)转化"无法访问地址"等提示信息!

二、源代码及案例分析

2.1FailureAnalysis源码介绍

IDEA下通过【Ctrl + shift + n】快捷键找到FailureAnalyzer源码,发现此接口只有一个方法,即将Throwable(发生了哪种异常)转换为FailureAnalysis(此异常描述)

packageorg.springframework.boot.diagnostics;
@FunctionalInterfacepublicinterfaceFailureAnalyzer {
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】找到其实现类

image.png

如上图,可以看到在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> {
@OverrideprotectedFailureAnalysisanalyze(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*/@Component@Order(100)
publicclassMyCommandLineRunnerimplementsCommandLineRunner {
privatestaticfinalLoggerlogger=LoggerFactory.getLogger(MyCommandLineRunner.class);
@AutowiredprivateSimpleMailsimpleMail;
@Overridepublicvoidrun(String... args) throwsException {
logger.debug("自定义CommandLineRunner");
thrownewMyFailureAnalyzerException("自定义错误分析器!");
    }
}

image.png

完结,1F00E8D0.gif

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
前端开发 Java fastjson
Netty系列(一):Springboot整合Netty,自定义协议实现
Netty是由JBOSS提供的一个java开源框架,现为 [Github](https://github.com/netty/netty)上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
958 0
Netty系列(一):Springboot整合Netty,自定义协议实现
|
Java Maven 开发工具
《SpringBoot系列七》:SprinBoot自定义自动装配类与xxx-spring-boot-starter
《SpringBoot系列七》:SprinBoot自定义自动装配类与xxx-spring-boot-starter
267 0
《SpringBoot系列七》:SprinBoot自定义自动装配类与xxx-spring-boot-starter
|
前端开发 Java Nacos
《SpringBoot系列三》:自定义配置时@Value和@ConfigurationProperties孰优孰劣?
《SpringBoot系列三》:自定义配置时@Value和@ConfigurationProperties孰优孰劣?
724 1
《SpringBoot系列三》:自定义配置时@Value和@ConfigurationProperties孰优孰劣?
|
Java Spring
Spring Boot 系列三:如何自定义一个SpringBoot Srarter
​ 目录 如何自定义一个SpringBoot Srarter? 首先创建一个项目,命名为demo-spring-boot-starter,引入SpringBoot相关依赖         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter</artifactId>         </dependency>         <dependency>             <groupId>org.spr
316 2
Spring Boot 系列三:如何自定义一个SpringBoot Srarter
|
Java Spring
自定义SpringBoot项目的启动Banner
``Banner``是``SpringBoot``框架一个特色的部分,其设计的目的无非就是一个框架的标识,其中包含了版本号、框架名称等内容,既然``SpringBoot``为我们提供了这个模块,它肯定也是可以更换的这也是``Spring``开源框架的设计理念。
|
Java Spring 容器
《SpringBoot系列十二》:如何自定义条件装配(由@ConditionalOnClass推导)
《SpringBoot系列十二》:如何自定义条件装配(由@ConditionalOnClass推导)
387 0
《SpringBoot系列十二》:如何自定义条件装配(由@ConditionalOnClass推导)
|
Java Spring
《SpringBoot系列十》:SpringBoot自定义Banner
《SpringBoot系列十》:SpringBoot自定义Banner
311 0
《SpringBoot系列十》:SpringBoot自定义Banner
|
NoSQL Java Redis
如何自定义一个SpringBoot中的starter
如何自定义一个SpringBoot中的starter
211 0
如何自定义一个SpringBoot中的starter
|
Java 数据库连接 Maven
SpringBoot——自定义一个spring-boot-starter包
SpringBoot——自定义一个spring-boot-starter包
560 0
SpringBoot——自定义一个spring-boot-starter包
|
监控 IDE Java
使用函数计算自定义运行时快速部署一个 SpringBoot 项目 | 文末有礼
本次体验,使用函数计算部署了一个 springboot 项目,总体来说过程很流畅,只需要在控制台点点就可以生成并部署好一个项目,对于新手来说非常的友好,省去一大堆传统部署项目时的环境搭建,安装依赖等等,可以真正的让我们做到只关心业务逻辑的开发!
使用函数计算自定义运行时快速部署一个 SpringBoot 项目 | 文末有礼