根據前面一篇總綱的博文,將總體結構劃分爲了四大塊,本文則主要目標集中在第一塊,報警執行器(AlarmExecute)的設計與加載上了java
主要的關注點無外乎 定義-》加載-》實現邏輯三塊了:git
在定義接口以前,先來根據幾個問題來加深下這個概念的理解:github
根據上面的基礎知識,那麼很容易給出接口的定義了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
表示獲取標識,默認給了一個實現,規則以下
Execute
(若是不是以這個結尾的就不須要了)SmsExecute -> SMS; LogExecute -> LOG;
上面接口定義中的sendMsg
中,支持給多個用戶發送報警信息,若是要求每一個報警信息都不一樣,好比最多見的是:緩存
固然這樣的場景徹底能夠本身在實現中來作框架
固然更激進一點就是,穿進來的title或者content做爲一個key,而後我能夠經過這個key,到其餘的地方(如db,緩存等)獲取報警內容,甚至我連傳進來的報警人都不care,直接從其餘地方來獲取ide
簡單來講,這個實現委託給用戶本身實現,你徹底能夠隨意的控制,作任何你想作的事情ui
加載AlarmExecut,貌似沒有什麼特別複雜的東西,通常的思路是建立一個簡單工廠類,而後實例化對應的Executor返回,(再多一點確保只有一個實例對象,加以緩存)this
這樣有什麼問題?.net
很簡單的實現,可是咱們須要加載用戶自定義的執行器,要怎麼支持呢?
幾種可行的解決手段
這個可算是最容易想到的了,直接讓用戶把本身的Executor實例,主動的扔進來
將前面說的簡單工廠,改爲抽象工廠類,讓後具體的加載委託給用戶本身來作
若是全部的AlarmExecute都委託給Spring容器來管理,那麼就很簡單了,直接經過ApplicationContext#getBean
來獲取全部的執行器便可
經過JDK的spi機制來實現(詳細後面來講)
針對上面的幾個手段,首先排除掉前面兩個,由於不知足咱們的設計目標一:
而後也排除掉spring容器,由於咱們但願這個東西,能夠較獨立的被引用到java工程中,後面能夠看狀況實現一個spring版
從使用來說,由spring容器來託管的方式,對使用者而言,是最簡單,成本最低的,由於不須要額外添加SPI配置
咱們採用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的惟一性判斷,不知足就直接拋出異常了
內部提供了兩個基本的報警實現,比較簡單
日誌報警執行器
/** * 有些報警,不須要當即上報,可是但願計數, 當大量出現時, 用於升級 * <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) { } }
AlarmExecute 的定義,加載以及實現規則目前都已經完成
IExecute
接口,內部邏輯無任務特殊要求,只是須要確保每一個executor的name惟一整個系統的第一步已經邁出,可是有個問題就是何時,纔會來調用 com.hust.hui.alarm.core.execut.SimpleExecuteFactory#getExecute
從而觸發執行器的加載呢?
盡信書則不如,已上內容,純屬一家之言,因本人能力通常,看法不全,若有問題,歡迎批評指正