事件驅(qū)動模型的初次使用

個人理解:事件驅(qū)動,顧名思義坠七,當(dāng)某一個事件發(fā)生時水醋,會有其他監(jiān)聽該事件發(fā)生的方法伴隨著該事件的執(zhí)行而觸發(fā)旗笔。因此,事件驅(qū)動中的組成就很明確了拄踪,一個事件蝇恶、一個調(diào)用事件的事件源、多個監(jiān)聽者惶桐。

案例:在我的工作中撮弧,有這樣的需求,一個游戲服務(wù)器需要對玩家的數(shù)據(jù)進行持久化姚糊,但是玩家的數(shù)據(jù)又會關(guān)聯(lián)到很多其他的實體贿衍,例如玩家的背包、寵物等等叛拷。如果將所有的保存邏輯都放在玩家數(shù)據(jù)保存的代碼塊中舌厨,那么每當(dāng)我新增一個關(guān)聯(lián)玩家的實體時岂却,就要添加新的代碼進來忿薇,這樣對于一個分模塊的游戲服務(wù)器來說是很不友好的。因此躏哩,我打算將保存玩家數(shù)據(jù)作為一個事件署浩,在我完成對玩家數(shù)據(jù)的保存操作以后就調(diào)用該事件,讓監(jiān)聽該事件的其他方法隨之觸發(fā)扫尺,例如保存背包和保存寵物等筋栋。這樣,只需要把這些方法注冊為保存玩家數(shù)據(jù)事件的監(jiān)聽者正驻,就能避免在玩家數(shù)據(jù)保存的代碼塊中不斷的新增其余的邏輯了弊攘。

代碼如下:我這是參考了Google的EventBus事件總線,自己寫了一個簡化版的事件驅(qū)動姑曙。

/**
 * 事件抽象類襟交,代表基礎(chǔ)事件
 *
 * @author Administrator
 */
public interface BaseEvent {

    /**
     * 得到事件的擁有者
     * @return
     */
    default Object getOwner() {
        return "system";
    }

}
/**
 * 利用Spring管理Bean,來對bean進行掃描伤靠,注冊監(jiān)聽者捣域。
 *
 * @author Administrator
 */
@Component("eventRegisterBeanProcessor")
public class EventRegisterBeanProcessor implements BeanPostProcessor {

    @Resource
    EventBus eventBus;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 注冊bean中存在@Subscribe注解的方法(注冊消息訂閱)
        eventBus.register(bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}
/**
 * 消息注冊器,用于尋找符合的Bean宴合,此處為尋找添加了@Subscribe注解的方法焕梅,并注冊進入集合當(dāng)中。
 * 具體的流程在代碼中有注釋說明卦洽。
 *
 * @author Administrator
 */
public class SubscriberRegistry {

    /**
     * 事件類型贞言,**所有** 消息訂閱者 的集合
     */
    private final Map<Class<? extends BaseEvent>, CopyOnWriteArraySet<Subscriber>> eventSubscribersMap = new ConcurrentHashMap<>();





    /**
     * 注冊bean中存在@Subscribe注解的方法(注冊消息訂閱)
     *
     * @param bean listener
     */
    public void register(Object bean) {
        Map<Class<? extends BaseEvent>, Subscriber> eventSubscriberMap = getAllEventSubscriber(bean);
        for (Entry<Class<? extends BaseEvent>, Subscriber> subscriberEntry : eventSubscriberMap.entrySet()) {
            Class<? extends BaseEvent> event = subscriberEntry.getKey();
            eventSubscribersMap.putIfAbsent(event, new CopyOnWriteArraySet<>());
            CopyOnWriteArraySet<Subscriber> subscriberSet = eventSubscribersMap.get(event);
            subscriberSet.add(subscriberEntry.getValue());
        }
    }

    // 注意:bean(也就是一個listener)中有method 和 @Subscribe注解 和 event事件參數(shù)
    // 1.遍歷bean中的method,找到有@Subscribe注解的method阀蒂,得到method當(dāng)中的event事件參數(shù)的 類型
    // 2.保存eventSubscribeMap =  map<event, new subscriber(bean, method)>
    // 3.遍歷eventSubscribeMap的event该窗,為每個event創(chuàng)建一個CopyOnWriteArraySet(如果未創(chuàng)建)
    // 4.將eventSubscribeMap的value保存進CopyOnWriteArraySet

    private Map<Class<? extends BaseEvent>, Subscriber> getAllEventSubscriber(Object bean) {
        Map<Class<? extends BaseEvent>, Subscriber> eventSubscribeMap = new HashMap<>();
        Method[] declaredMethods = bean.getClass().getDeclaredMethods();
        for (Method method : declaredMethods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 如果方法method上有@Subscribe注解打肝,即消息處理
            if (method.isAnnotationPresent(Subscribe.class)) {
                if (parameterTypes.length != 1) {
                    throw new IllegalArgumentException();
                }
                // 如果參數(shù)是event類型
                if (BaseEvent.class.isAssignableFrom(parameterTypes[0])) {
                    // 得到event類型
                    Class<? extends BaseEvent> event = (Class<? extends BaseEvent>) parameterTypes[0];
                    // 添加進map
                    eventSubscribeMap.put(event, new Subscriber(bean, method));
                }
            }
        }
        return eventSubscribeMap;
    }

    /**
     * 根據(jù)event類型找到所有的消息執(zhí)行方法
     * @param eventType
     * @return
     */
    public Set<Subscriber> getSubscribersByEvent(Class<? extends BaseEvent> eventType) {
        if (!eventSubscribersMap.containsKey(eventType)) {
            return Collections.emptySet();
        }
        return eventSubscribersMap.get(eventType);
    }

}
/**
 * @Subscribe注解,即文中提到的監(jiān)聽者挪捕,注解的方法會伴隨著事件的執(zhí)行而觸發(fā)粗梭。
 * 訂閱消息(行為)
 * 一個被標(biāo)注了@Subscribe注解的方法就是一個訂閱者。
 *
 * @author Administrator
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {

}
/**
 * 消息訂閱者(類方法)
 * 實際上就是監(jiān)聽者的類级零。
 * 可以這樣理解断医,監(jiān)聽者所對應(yīng)的對象。
 * 即奏纪,method對應(yīng)標(biāo)注了@Subscribe注解的方法鉴嗤。
 * listener對應(yīng)其所在的類。
 * 舉個例子:Test類中有一個test()方法序调,test()方法是被標(biāo)注了@Subscribe注解的醉锅。
 * 那么,此處就會有一個Subscriber對象发绢,listener就是Test類硬耍;method就是test()方法。
 *
 * @author Administrator
 */
public class Subscriber {

    /**
     * 消息監(jiān)聽器
     */
    private Object listener;

    /**
     * 消息執(zhí)行者
     */
    private Method method;

    public Subscriber(Object listener, Method method) {
        this.listener = listener;
        this.method = method;
    }

    /**
     * 執(zhí)行消息
     * @param event 事件
     */
    public void handleEvent(BaseEvent event) {
        try {
            method.invoke(listener, event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 事件總線边酒,用于注冊監(jiān)聽者以及處理事件经柴。
 *
 * @author Administrator
 */
@Slf4j
@Component("eventBus")
public class EventBus {


    private ThreadPoolExecutor threadPoolExecutor;

    /**
     * 初始化事件總線線程池
     */
    public void initEventBusPool() {
        ThreadFactory businessThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("EventBus thread-%d")
                .setUncaughtExceptionHandler((thread, exception) -> exception.printStackTrace())
                .build();
        // 創(chuàng)建線程數(shù)量為1、無界任務(wù)隊列的線程池墩朦。
        threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), businessThreadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
    }

    private SubscriberRegistry registry = new SubscriberRegistry();

    /**
     * 注冊的目的是坯认,通過事件的類型,找到 所有 對應(yīng)的subscriber(跟事件關(guān)聯(lián)的方法)
     * @param bean
     */
    public void register(Object bean) {
        registry.register(bean);
    }


    /**
     * 處理消息
     * 當(dāng)有事件發(fā)布到事件總線中氓涣,事件總線遍歷所有的訂閱者進行事件處理
     *
     * @param event
     */
    public void post(BaseEvent event) {
        // 得到事件類型
        Class<? extends BaseEvent> eventType = event.getClass();
        // 通過事件類型找到所有要處理的消息(事件訂閱者)
        Set<Subscriber> subscribers = registry.getSubscribersByEvent(eventType);
        for (Subscriber subscriber : subscribers) {
            // 處理事件
            subscriber.handleEvent(event);
        }
    }

    /**
     * 異步處理事件
     * @param event
     */
    public void asyncPost(BaseEvent event) {
        this.threadPoolExecutor.execute(() -> post(event));
    }
}
/**
 * 一個測試Subscriber
 * @author Administrator
 */
@Slf4j
public class Test {

    /**
    * 測試案例中的保存玩家數(shù)據(jù)
    */
    @Subscribe
    public void testPlayerSaveEvent(PlayerSaveEvent playerSaveEvent) {
        Player p = playerSaveEvent.getOwner();
        log.info("=========" + p.getName() + "=========");
    }

}
/**
 * 角色數(shù)據(jù)保存事件
 *
 * @author Administrator
 */
public class PlayerSaveEvent implements BaseEvent {

    private Player player;

    public PlayerSaveEvent(Player player) {
        this.player = player;
    }

    @Override
    public Player getOwner() {
        return player;
    }
}
/**
* 玩家管理類牛哺,保存玩家數(shù)據(jù),并調(diào)用對應(yīng)的保存事件
*/
public TestSavePlayer {
    
    /**
     * 保存角色數(shù)據(jù)
     * 不管角色增加了什么劳吠,都不影響引润,不用修改這里的代碼。
     * 保存角色是一個事件赴背,用監(jiān)聽器監(jiān)聽保存角色椰拒,一旦監(jiān)聽成功,驅(qū)動保存背包凰荚、保存寵物等事件一起執(zhí)行
     *
     * @param playerId
     */
    public void savePlayer(Long playerId) {
        // 通過playerId得到Player
        Player player = playerCache.getPlayerByPlayerId(playerId);
        if (player != null) {
            playerDao.save(dbPlayer);
            // 處理玩家數(shù)據(jù)保存事件
            eventBus.post(new PlayerSaveEvent(player));

        }
    }
}

代碼就是這樣燃观,一旦保存了玩家數(shù)據(jù),即調(diào)用了TestSavePlayer中的savePlayer()方法時便瑟,Test中的testPlayerSaveEvent()方法就會隨之觸發(fā)缆毁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市到涂,隨后出現(xiàn)的幾起案子脊框,更是在濱河造成了極大的恐慌颁督,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浇雹,死亡現(xiàn)場離奇詭異沉御,居然都是意外死亡,警方通過查閱死者的電腦和手機昭灵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門吠裆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烂完,你說我怎么就攤上這事试疙。” “怎么了抠蚣?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵祝旷,是天一觀的道長。 經(jīng)常有香客問我嘶窄,道長怀跛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任护侮,我火速辦了婚禮敌完,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羊初。我一直安慰自己,他們只是感情好什湘,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布长赞。 她就那樣靜靜地躺著,像睡著了一般闽撤。 火紅的嫁衣襯著肌膚如雪得哆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天哟旗,我揣著相機與錄音贩据,去河邊找鬼。 笑死闸餐,一個胖子當(dāng)著我的面吹牛饱亮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舍沙,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼近上,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拂铡?” 一聲冷哼從身側(cè)響起壹无,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤葱绒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后斗锭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體地淀,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年岖是,在試婚紗的時候發(fā)現(xiàn)自己被綠了骚秦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡璧微,死狀恐怖作箍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情前硫,我是刑警寧澤胞得,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站屹电,受9級特大地震影響阶剑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜危号,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一牧愁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧外莲,春花似錦猪半、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至声邦,卻和暖如春乏奥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亥曹。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工邓了, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人媳瞪。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓骗炉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親材失。 傳聞我的和親對象是個殘疾皇子痕鳍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354