報警系統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

相關文章
相關標籤/搜索