报警系统QuickAlarm之报警执行器的设计与实现

根据前面一篇总纲的博文,将总体结构划分为了四大块,本文则主要目标集中在第一块,报警执行器(AlarmExecute)的设计与加载上了java

主要的关注点无外乎 定义-》加载-》实现逻辑三块了:git

  • AlarmExecute 的接口定义
  • 如何加载用户自定义的AlarmExecute
  • AlarmExecute的内部实现

I. AlarmExecute接口定义

在定义接口以前,先来根据几个问题来加深下这个概念的理解:github

1. 基础知识

  1. 说一下这个报警执行器究竟是干吗的?
  • 执行具体的报警逻辑(感受说了依据废话)
  • 所以不一样的报警方式,能够选择不一样的实现,这个强业务关联的逻辑能够交由适用方本身来把控
  1. 多个alarmExecute之间如何区分?
  • 给一个相似身份证的标识,将标识与alarmExecute绑定,则能够报警规则中,用这个标识来表示对应的报警执行器
  • 标识要求全局惟一,不然就无法找到对应的执行器

2. 接口定义

根据上面的基础知识,那么很容易给出接口的定义了spring

public interface IExecute {

    /**
     * 报警的具体实现
     *
     * @param users 报警用户,支持批量
     * @param title 报警信息的title
     * @param msg   报警的主题信息
     */
    void sendMsg(List<String> users, String title, String msg);


    /**
     * 获取报警单元惟一标识
     *
     * @return name  要求全局惟一
     */
    default String getName() {
        return ExecuteNameGenerator.genExecuteName(this.getClass());
    }
}
  • 第一个方法sendMsg也就是须要使用者来实现的具体执行报警代码的核心模块了,比较清晰,其中用户是列表,所以,支持同时报警给多个用户(可是报警内容都是相同的)
  • 第二个方法getName表示获取标识,默认给了一个实现,规则以下
    • 获取类的 SimpleName
    • 干掉类名后面的 Execute (若是不是以这个结尾的就不须要了)
    • 剩下的所有转大写
    • 实例: SmsExecute -> SMS; LogExecute -> LOG;

3. 额外说明

上面接口定义中的sendMsg中,支持给多个用户发送报警信息,若是要求每一个报警信息都不一样,好比最多见的是:缓存

  • 发送一段文本,其中通知人地方根据报警人来替换,其余的不变

固然这样的场景彻底能够本身在实现中来作框架

  • 传入的content做为一个话术模板
  • 而后利用 String#format() 来实现参数代替

固然更激进一点就是,穿进来的title或者content做为一个key,而后我能够经过这个key,到其余的地方(如db,缓存等)获取报警内容,甚至我连传进来的报警人都不care,直接从其余地方来获取ide

简单来讲,这个实现委托给用户本身实现,你彻底能够随意的控制,作任何你想作的事情ui

II. AlarmExecute的加载

1. 问题分析

加载AlarmExecut,貌似没有什么特别复杂的东西,通常的思路是建立一个简单工厂类,而后实例化对应的Executor返回,(再多一点确保只有一个实例对象,加以缓存)this

这样有什么问题?.net

很简单的实现,可是咱们须要加载用户自定义的执行器,要怎么支持呢?

几种可行的解决手段

1. 开放一个注册接口

这个可算是最容易想到的了,直接让用户把本身的Executor实例,主动的扔进来

2. 抽象工厂

将前面说的简单工厂,改为抽象工厂类,让后具体的加载委托给用户本身来作

3. 借助Spring容器来加载

若是全部的AlarmExecute都委托给Spring容器来管理,那么就很简单了,直接经过ApplicationContext#getBean来获取全部的执行器便可

4. SPI加载方式

经过JDK的spi机制来实现(详细后面来讲)

针对上面的几个手段,首先排除掉前面两个,由于不知足咱们的设计目标一:

  • 简单 (只有报警这个接口进行交互,不须要额外的接口调用)

而后也排除掉spring容器,由于咱们但愿这个东西,能够较独立的被引用到java工程中,后面能够看状况实现一个spring版

从使用来说,由spring容器来托管的方式,对使用者而言,是最简单,成本最低的,由于不须要额外添加SPI配置


2. 实现

咱们采用SPI方式来实现加载,对于SPI是什么东西,这里不详细展看,有兴趣的童鞋能够看我以前的一个系类博文:自定义SPI框架设计

实现方式,可说是很是简单了

public class SimpleExecuteFactory {
    private static Map<String, IExecute> cacheMap;
    private static void loadAlarmExecute() {
        Map<String, IExecute> map = new HashMap<>();
        Iterator<IExecute> iExecutes = ServiceLoader.load(IExecute.class).iterator();
        IExecute tmp;
        while (iExecutes.hasNext()) {
            tmp = iExecutes.next();
            if (!map.containsKey(tmp.getName())) {
                map.put(tmp.getName(), tmp);
            } else {
                throw new DuplicatedAlarmExecuteDefinedException(
                        "duplicated alarm execute defined!" +
                                "\n" +
                                ">>name:" +
                                tmp.getName() +
                                ">>>clz:" +
                                tmp.getClass() +
                                ">>>clz:" +
                                map.get(tmp.getName())
                );
            }
        }

        cacheMap = map;
    }

    public static IExecute getExecute(String execute) {
        if (cacheMap == null) {
            synchronized (SimpleExecuteFactory.class) {
                if (cacheMap == null) {
                    loadAlarmExecute();
                }
            }
        }

        // 若是不存在,则降级为 LogExecute
        IExecute e = cacheMap.get(execute);
        return e == null ? cacheMap.get(LogExecute.NAME) : e;
    }
}

上面对外就暴露一个方法,内部比较简单,若是传入标识对应的报警器没有,则返回一个默认的,确保不会所以挂掉

经过SPI加载全部的执行器的逻辑就一行

Iterator<IExecute> iExecutes = ServiceLoader.load(IExecute.class).iterator();

而后须要关注的是循环内部,作了name的惟一性判断,不知足就直接抛出异常了

III. AlarmExecute内部实现

内部提供了两个基本的报警实现,比较简单

日志报警执行器

/**
 * 有些报警,不须要当即上报,可是但愿计数, 当大量出现时, 用于升级
 * <p/>
 * Created by yihui on 2017/4/28.
 */
public class LogExecute implements IExecute {
    public static final String NAME = ExecuteNameGenerator.genExecuteName(LogExecute.class);

    private static final Logger logger = LoggerFactory.getLogger("alarm");

    @Override
    public void sendMsg(List<String> users, String title, String msg) {
        logger.info("Do send msg by {} to user:{}, title: {}, msg: {}", getName(), users, title, msg);
    }
}

空报警执行器

/**
 * 空报警执行器, 什么都不干
 * <p>
 * Created by yihui on 2017/5/12.
 */
public class NoneExecute implements IExecute {
    public static final String NAME = ExecuteNameGenerator.genExecuteName(NoneExecute.class);

    @Override
    public void sendMsg(List<String> users, String title, String msg) {

    }
}

IV. 小结

AlarmExecute 的定义,加载以及实现规则目前都已经完成

  • 定义:两个方法,一个执行报警方法,一个返回惟一标识方法
  • 加载:经过SPI方式加载全部定义的alarmExecute
  • 实现:由用户自定义实现IExecute接口,内部逻辑无任务特殊要求,只是须要确保每一个executor的name惟一

整个系统的第一步已经迈出,可是有个问题就是何时,才会来调用 com.hust.hui.alarm.core.execut.SimpleExecuteFactory#getExecute 从而触发执行器的加载呢?

IMAGE

V. 其余

相关博文

  1. 报警系统QuickAlarm总纲
  2. 报警系统QuickAlarm之报警执行器的设计与实现
  3. 报警系统QuickAlarm之报警规则的设定与加载
  4. 报警系统QuickAlarm之报警规则解析
  5. 报警系统QuickAlarm之频率统计及接口封装
  6. 报警系统QuickAlarm使用手册

项目

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力通常,看法不全,若有问题,欢迎批评指正

扫描关注,java分享

QrCode

相关文章
相关标签/搜索