报警系统QuickAlarm之报警执行器的设计与实现
根据前面一篇总纲的博文,将整体结构划分为了四大块,本文则主要目标集中在第一块,报警执行器(AlarmExecute)的设计与加载上了
主要的关注点无外乎 定义-》加载-》实现逻辑三块了:
- AlarmExecute 的接口定义
- 如何加载用户自定义的AlarmExecute
- AlarmExecute的内部实现
I. AlarmExecute接口定义
在定义接口之前,先来根据几个问题来加深下这个概念的理解:
1. 基础知识
- 说一下这个报警执行器到底是干嘛的?
- 执行具体的报警逻辑(感觉说了依据废话)
- 因此不同的报警方式,可以选择不同的实现,这个强业务关联的逻辑可以交由适用方自己来把控
- 多个alarmExecute之间如何区分?
- 给一个类似身份证的标识,将标识与alarmExecute绑定,则可以报警规则中,用这个标识来表示对应的报警执行器
- 标识要求全局唯一,否则就没法找到对应的执行器
2. 接口定义
根据上面的基础知识,那么很容易给出接口的定义了
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,直接从其他地方来获取
简单来说,这个实现委托给用户自己实现,你完全可以随意的控制,做任何你想做的事情
II. AlarmExecute的加载
1. 问题分析
加载AlarmExecut,貌似没有什么特别复杂的东西,一般的思路是创建一个简单工厂类,然后实例化对应的Executor返回,(再多一点确保只有一个实例对象,加以缓存)
这样有什么问题?
很简单的实现,但是我们需要加载用户自定义的执行器,要怎么支持呢?
几种可行的解决手段
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
从而触发执行器的加载呢?
- HDU----(4549)M斐波那契数列(小费马引理+快速矩阵幂)
- Centos系统修改时区
- zookeeper思考与总结1:在其它组件的作用及hdfs对比
- HDU----(4291)A Short problem(快速矩阵幂)
- Linux下删除指定文件之外的其他文件
- HDU----(2157)How many ways??(快速矩阵幂)
- 试试Linux下的ip命令
- hdu---(2604)Queuing(矩阵快速幂)
- centos7下卸载python后yum不能使用的恢复方法
- hdu---(5038)Grade(胡搞)
- shell生成随机字符的几种方法
- Python时间获取及转换
- spark streaming知识总结[优化]
- 让你真正明白spark streaming
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android 自定义AlertDialog对话框样式
- Android自定义一个图形单点移动缩小的效果
- 详解Android实现购物车页面及购物车效果(点击动画)
- Android利用LitePal操作数据库存取图片
- Android 改变图标原有颜色和搜索框的实例代码
- Android自定义滑动验证条的示例代码
- Android实现图片转高斯模糊以及高斯模糊布局
- android多媒体类VideoView使用方法详解
- Android编程实现短信收发及语音播报提示功能示例
- Android viewpager无限轮播获取网络图片功能
- Android 使用ContentObserver监听数据库内容是否更改
- Android UI中TextView的使用方法
- Android 中通过ViewDragHelper实现ListView的Item的侧拉划出效果
- 浅谈Android中使用异步线程更新UI视图的几种方法
- Android gradle打包并自动上传的方法