SpringBoot之"How to"系列(一)---自定义FailureAnalyzer-阿里云开发者社区

开发者社区> 数据库> 正文
登录阅读全文

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

简介: FailureAnalyzer是什么、自定义FailureAnalyzer如何实现

一、FailureAnalyzer是什么

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

二、源代码及案例分析

2.1 FailureAnalysis源码介绍

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

package org.springframework.boot.diagnostics;

@FunctionalInterface
public interface FailureAnalyzer {
    FailureAnalysis analyze(Throwable failure);
}

package org.springframework.boot.diagnostics;

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;
    }
    //...省略get方法
}

2.2 FailureAnalysis开源实现类介绍

通过查找FailureAnalyzer【alt+f7】找到其实现类

image.png

如上图,可以看到在META-INF下有很多SpringBoot出厂就定义好的【启动失败分析器】,这里我们选取RedisUrlSyntaxFailureAnalyzer这个来看看,如下。

建议先看第二个代码片段(AbstractFailureAnalyzer),在回过头看如下片段。这里这样排版是为了展示查找的逻辑,也就是寻根问底模式!


//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure.data.redis;

import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;

/**
  可以看到这里的泛型为RedisUrlSyntaxException
*/
class RedisUrlSyntaxFailureAnalyzer extends AbstractFailureAnalyzer<RedisUrlSyntaxException> {
    RedisUrlSyntaxFailureAnalyzer() {
    }

    /**
    *  具体的FailureAnalysis生成操作。
       这里我们可以看到,Redis启动失败分析器分为了三种情况!
    */
    protected FailureAnalysis analyze(Throwable rootFailure, RedisUrlSyntaxException cause) {
        try {
            URI uri = new URI(cause.getUrl());
            if ("redis-sentinel".equals(uri.getScheme())) {
                return new FailureAnalysis(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())) {
                return new FailureAnalysis(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())) {
                return new FailureAnalysis(this.getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()), "Use the scheme 'redis://` for insecure or `rediss://` for secure Redis standalone configuration.", cause);
            }
        } catch (URISyntaxException var4) {
        }

        return new FailureAnalysis(this.getDefaultDescription(cause.getUrl()), "Review the value of the property 'spring.redis.url'.", cause);
    }

    /**
      这里就是启动时如果失败,给出的友好提示信息语句
    */
    private String getDefaultDescription(String url) {
        return "The URL '" + url + "' is not valid for configuring Spring Data Redis. ";
    }

    private String getUnsupportedSchemeDescription(String url, String scheme) {
        return this.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)
//

package org.springframework.boot.diagnostics;

import org.springframework.core.ResolvableType;

public abstract class AbstractFailureAnalyzer<T extends Throwable> implements FailureAnalyzer {
    public AbstractFailureAnalyzer() {
    }

    /**
    *   1.找到泛型异常对象T
        2.将具体操作留给子类
    **/
    public FailureAnalysis analyze(Throwable failure) {
        T cause = this.findCause(failure, this.getCauseType());
        return cause != null ? this.analyze(failure, cause) : null;
    }

    /*
     * 模板(钩子)方法,将具体的操作留给子类
    */
    protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause);

    /**
      * 获取AbstractFailureAnalyzer的类类型
    */
    protected Class<? extends T> getCauseType() {
        return ResolvableType.forClass(AbstractFailureAnalyzer.class, this.getClass()).resolveGeneric(new int[0]);
    }

    /**
    * 如果是AbstractFailureAnalyzer类型,则直接返回,否则直接返回Throwable
      这里可以理解发生的异常处理类(即我们自己实现的FailureAnalyzer)是否继承了AbstractFailureAnalyzer
    */
    protected final <E extends Throwable> E findCause(Throwable failure, Class<E> type) {
        while(failure != null) {
            if (type.isInstance(failure)) {
                return failure;
            }

            failure = failure.getCause();
        }

        return null;
    }
}

在回过头来看RedisUrlSyntaxFailureAnalyzer上RedisUrlSyntaxException,这就是自定义的异常!

package org.springframework.boot.autoconfigure.data.redis;

class RedisUrlSyntaxException extends RuntimeException {
    /**
     自定义异常属性
     Redis启动时候就需要连接某个地址,所以这里需要url这个属性。
     自己业务有需要也可以增加属性
    */
    private final String url;

    RedisUrlSyntaxException(String url, Exception cause) {
        super(buildMessage(url), cause);
        this.url = url;
    }

    RedisUrlSyntaxException(String url) {
        super(buildMessage(url));
        this.url = url;
    }

    String getUrl() {
        return this.url;
    }

    private static String buildMessage(String url) {
        return "Invalid Redis URL '" + url + "'";
    }
}

三、自定义启动失败分析器

根据上面的分析可知,要想自定义一个启动失败分析器,需要两个类(自定义异常类、失败分析器)、一个配置文件(spring.factories)。开干!

3.1自定义异常类

package com.tab343.myspringboot.analyzer;

/**
 * @Description : 自定义启动分析异常
 */
public class MyFailureAnalyzerException extends RuntimeException{

    /**
       自定义异常属性,这里我只增加了name,读者按需增加!
    */
    private final String name;

    public MyFailureAnalyzerException(String name, Exception cause) {
        super(buildMessage(name), cause);
        this.name = name;
    }

    public MyFailureAnalyzerException(String name) {
        super(buildMessage(name));
        this.name = name;
    }

    public String getName() {
        return name;
    }

    private static String buildMessage(String name) {
        return "发生了 【" + name + "】错误。";
    }
}

3.2启动错误分析器

package com.tab343.myspringboot.analyzer;

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

/**
 * 自定义启动失败分析器
 */
public class MyFailureAnalyzer extends AbstractFailureAnalyzer<MyFailureAnalyzerException> {

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, MyFailureAnalyzerException cause) {
        return new FailureAnalysis(cause.getName(), "看一哈,出错了喂!", cause);
    }

}

3.3配置文件

在resources目录下创建META-INF/spring.factories文件,内容如下。

org.springframework.boot.diagnostics.FailureAnalyzer=\
  com.tab343.myspringboot.analyzer.MyFailureAnalyzer

四、效果

通过自定义CommandLineRunner实现程序启动就抛出MyFailureAnalyzerException异常,达到启动就失败的场景!

package com.tab343.myspringboot.runner;

import com.tab343.myspringboot.analyzer.MyFailureAnalyzerException;
import com.tab343.myspringboot.mail.SimpleMail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 类描述:
 *
 * @author sevensun
 * @version 1.0
 * @date 2020/10/27 下午8:35
 */
@Component
@Order(100)
public class MyCommandLineRunner implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(MyCommandLineRunner.class);

    @Autowired
    private SimpleMail simpleMail;

    @Override
    public void run(String... args) throws Exception {
        logger.debug("自定义CommandLineRunner");
        throw new MyFailureAnalyzerException("自定义错误分析器!");
    }
}

image.png

完结,1F00E8D0.gif

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: