【啃啊啃 Spring5 源碼】細(xì)碎一:spring 事件機(jī)制

閱讀spring源碼時(shí),看到ApplicationEvent相關(guān)的代碼覺(jué)得熟悉又困惑,深入了解了一下,發(fā)現(xiàn)原來(lái)是spring事件機(jī)制(原諒我之前沒(méi)用過(guò)……)。
這里在【Spring4揭秘 基礎(chǔ)1】監(jiān)聽(tīng)器和事件的基礎(chǔ)下進(jìn)行一下擴(kuò)展深入,感謝這篇博文的作者,他的spring基礎(chǔ)系列文章讓我在閱讀源碼時(shí),輕松了不少。

注:源碼部分根據(jù)spring-5.0.7版本分析

設(shè)計(jì)模式

spring事件機(jī)制其實(shí)就是觀察者模式的一種體現(xiàn)。忘記或不熟悉觀察者模式的朋友可以看我前面的總結(jié):Head First 設(shè)計(jì)模式(二)觀察者模式

觀察者模式簡(jiǎn)單可分為兩部分:主題觀察者。當(dāng)一個(gè)主題改變狀態(tài)時(shí),它的所有依賴(lài)者都會(huì)收到通知并進(jìn)行自動(dòng)更新。

Spring事件機(jī)制簡(jiǎn)單可分為三部分:事件廣播觀察者。 “主題改變狀態(tài)” 這個(gè)動(dòng)作被抽離成了 一個(gè)“事件”,由一個(gè)持有所有觀察者的“廣播容器” 進(jìn)行廣播,“觀察者”們 接收到相應(yīng)事件后進(jìn)行自動(dòng)更新。

這種設(shè)計(jì)其實(shí)繼承自Java本身的事件機(jī)制:

  1. java.util.EventObject
    事件狀態(tài)對(duì)象的基類(lèi),它封裝了事件源對(duì)象以及和事件相關(guān)的信息。所有java的事件類(lèi)都需要繼承該類(lèi)。
  2. java.util.EventListener
    觀察者基類(lèi),當(dāng)事件源的屬性或狀態(tài)改變的時(shí)候,調(diào)用相應(yīng)觀察者內(nèi)的回調(diào)方法。
  3. Source
    主題類(lèi),java中未定義,持有所有的觀察者,當(dāng)主題狀態(tài)發(fā)生改變,產(chǎn)生事件,負(fù)責(zé)向所有觀察者發(fā)布事件

Java的事件機(jī)制這里不敞開(kāi)講,想了解可看:java 事件機(jī)制

Spring中的事件機(jī)制

Spring的事件機(jī)制相關(guān)的核心類(lèi)有四個(gè):

  • ApplicationEvent: Spring中的事件基類(lèi),繼承自java.util.EventObject,創(chuàng)建是需要指定事件源
public abstract class ApplicationEvent extends EventObject {
    /**
     * 創(chuàng)建一個(gè)事件,需要指定事件源
     */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
}
  • ApplicationEventPublisher:發(fā)布事件者,調(diào)用廣播發(fā)布事件
public interface ApplicationEventPublisher {
    /**發(fā)布事件*/
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }
    void publishEvent(Object event);
}
  • ApplicationEventMulticaster:廣播,持有觀察者集合,可向集合內(nèi)的所有觀察者通知事件
public interface ApplicationEventMulticaster {
    /**
     * 添加監(jiān)聽(tīng)者(觀察者)
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * 刪除監(jiān)聽(tīng)者(觀察者)
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * 向所有監(jiān)聽(tīng)者發(fā)布事件
     */
    void multicastEvent(ApplicationEvent event);
}
  • ApplicationListener:觀察者,接收對(duì)應(yīng)事件后,執(zhí)行邏輯
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    /**
     * 接收事件后,執(zhí)行相應(yīng)邏輯
     */
    void onApplicationEvent(E event);
}

事件發(fā)布者ApplicationEventPublisher持有廣播,而廣播ApplicationEventMulticaster持有若干觀察者ApplicationListener。一個(gè)事件ApplicationEvent可以通過(guò)發(fā)布者ApplicationEventPublisher發(fā)布后,會(huì)調(diào)用廣播ApplicationEventMulticaster通知所有觀察者,觀察者ApplicationListener收到通知后執(zhí)行相關(guān)操作。

下面舉例說(shuō)明:
當(dāng)一個(gè)用戶(hù)注冊(cè)結(jié)束后,我們想要將這個(gè)事件發(fā)生給短信監(jiān)聽(tīng)者和郵件監(jiān)聽(tīng)者,讓他們向用戶(hù)發(fā)送短信和郵件。

public class EventDemo {

    public static void main(String[] args) {
        //構(gòu)建廣播器
        ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        //廣播添加監(jiān)聽(tīng)器
        multicaster.addApplicationListener(new RegisterListener1());
        multicaster.addApplicationListener(new RegisterListener2());

        //構(gòu)建事件發(fā)布者
        MyEventPublicsher eventPublicsher = new MyEventPublicsher();
        //事件發(fā)布者增加廣播
        eventPublicsher.setEventMulticaster(multicaster);

        //構(gòu)建注冊(cè)事件
        User user = new User("jack", "18782252509", "jack_email@163.com");
        System.out.println("用戶(hù)注冊(cè)……");
        RegisterEvent registerEvent = new RegisterEvent(user);

        //發(fā)布注冊(cè)事件
        eventPublicsher.publishEvent(registerEvent);
    }

    /**
     * 用戶(hù)實(shí)體類(lèi)
     */
    public static class User{
        private String id;
        private String name;
        private String phone;
        private String email;

        public User(String name, String phone, String email) {
            this.name = name;
            this.phone = phone;
            this.email = email;
        }

        //.....GET AND SET

    }
    /**
     * 自定義注冊(cè)事件
     */
    public static class RegisterEvent extends ApplicationEvent {
        //事件的構(gòu)造方法中,必須制定事件源
        public RegisterEvent(User user) {
            super(user);
        }

        public User getUser(){
            return (User) getSource();
        }
    }

    /**
     * 注冊(cè)事件監(jiān)聽(tīng)者1-短信監(jiān)聽(tīng)者(即觀察者),負(fù)責(zé)注冊(cè)后發(fā)生短信
     * 注意:實(shí)現(xiàn)接口時(shí),在泛形中指定事件類(lèi)型,則只監(jiān)聽(tīng)該類(lèi)型事件。若不指定,則默認(rèn)監(jiān)聽(tīng)所有事件。
     */
    public static class RegisterListener1 implements ApplicationListener<RegisterEvent> {
        public void onApplicationEvent(RegisterEvent event) {
            User user = event.getUser();
            System.out.println("用戶(hù):"+ user.getName()+"注冊(cè)結(jié)束,向手機(jī)"+user.getPhone()+"發(fā)送短信!");
        }
    }

    /**
     * 注冊(cè)事件監(jiān)聽(tīng)者2-郵件監(jiān)聽(tīng)者(即觀察者),負(fù)責(zé)注冊(cè)后發(fā)送郵件
     * 注意:實(shí)現(xiàn)接口時(shí),在泛形中指定事件類(lèi)型,則只監(jiān)聽(tīng)該類(lèi)型事件。若不指定,則默認(rèn)監(jiān)聽(tīng)所有事件。
     */
    public static class RegisterListener2 implements ApplicationListener<RegisterEvent> {

        public void onApplicationEvent(RegisterEvent event) {
            User user = event.getUser();
            System.out.println("用戶(hù):"+ user.getName()+"注冊(cè)結(jié)束,發(fā)生郵件:"+user.getEmail());
        }
    }


    /**
     * 事件發(fā)布者,持有監(jiān)聽(tīng)者
     */
    public static class MyEventPublicsher implements  ApplicationEventPublisher{
        //廣播
        private ApplicationEventMulticaster eventMulticaster;

        public void setEventMulticaster(ApplicationEventMulticaster eventMulticaster) {
            this.eventMulticaster = eventMulticaster;
        }

        //發(fā)布事件
        public void publishEvent(Object event) {
            eventMulticaster.multicastEvent((ApplicationEvent) event);
        }
    }

}

輸出:

用戶(hù)注冊(cè)后
用戶(hù):jack注冊(cè)結(jié)束,向手機(jī)18782252509發(fā)送短信!
用戶(hù):jack注冊(cè)結(jié)束,發(fā)生郵件:jack_email@163.com

源碼細(xì)節(jié)解析

我們主要分析下廣播的細(xì)節(jié),以SimpleApplicationEventMulticaster為例:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
        @Override
        public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
            //事件被統(tǒng)一封裝成了ResolvableType,方便形參入口統(tǒng)一
            ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
            //根據(jù)事件類(lèi)型,通過(guò)泛形反射獲取對(duì)應(yīng)的監(jiān)聽(tīng)者
            for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
                //獲取廣播配置的線(xiàn)程池
                Executor executor = getTaskExecutor();
                //如果有配置線(xiàn)程池,則異步通知事件監(jiān)聽(tīng)者
                if (executor != null) {
                    executor.execute(() -> invokeListener(listener, event));
                }
                //沒(méi)有配置線(xiàn)程池,同步通知事件監(jiān)聽(tīng)者
                else {
                    invokeListener(listener, event);
                }
            }
        }
        
        private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
                try {
                    //執(zhí)行監(jiān)聽(tīng)者對(duì)應(yīng)邏輯
                    listener.onApplicationEvent(event);
                }
                catch (ClassCastException ex) {
                    ……
                }
            }
}

我們可以看到spring廣播時(shí),會(huì)先去判斷有沒(méi)有配置線(xiàn)程池,如果配置則使用線(xiàn)程池異步執(zhí)行監(jiān)聽(tīng)者邏輯,否則同步。

需要注意的是,我們使用spring事件機(jī)制時(shí),默認(rèn)是沒(méi)有配置線(xiàn)程池的,也就是默認(rèn)所有的通知都是同步的,需要手動(dòng)指定線(xiàn)程池才會(huì)開(kāi)啟同步。

應(yīng)用

設(shè)計(jì)一個(gè)業(yè)務(wù)場(chǎng)景:當(dāng)一個(gè)用戶(hù)完成貸款訂單后,我們希望執(zhí)行發(fā)送提醒短信、調(diào)用積分服務(wù)增加積分、通知風(fēng)控服務(wù)重算風(fēng)控值(后續(xù)操作可能增加)等功能。這種業(yè)務(wù)需求開(kāi)始很可能寫(xiě)成同步代碼。

//創(chuàng)建訂單
public void createOrder(Order order){
    創(chuàng)建貸款訂單;
    發(fā)送提醒短信;
    調(diào)用積分服務(wù)增加積分;
    調(diào)用風(fēng)控服務(wù)推送訂單信息;
    ……
    返回;
}

隨著業(yè)務(wù)復(fù)雜度的增加,我們很快發(fā)現(xiàn)createOrder()這個(gè)方法耦合了太多與創(chuàng)建訂單無(wú)關(guān)的邏輯,即影響了原本創(chuàng)建訂單方法的效率,在設(shè)計(jì)上又不符合“開(kāi)閉原則”。

現(xiàn)在使用spring事件機(jī)制我們來(lái)解耦,將與注冊(cè)無(wú)關(guān)的操作改為異步。這里直接使用注解式寫(xiě)法。

  • 首先我們修改spring中的廣播,為它注入我們自定義的線(xiàn)程池,在spring配置加上:
    <!--自定義線(xiàn)程池-->
    <bean id="myExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" />

    <!--修改容器中的廣播,注入自定義線(xiàn)程池-->
    <bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
        <property name="taskExecutor" ref="myExecutor" />
    </bean>
  • 定義一個(gè)創(chuàng)建訂單事件
/**
 * 創(chuàng)建訂單完成事件
 */
@Component
public class AfterCreateOrderEvent extends ApplicationEvent {

    public AfterCreateOrderEvent(Order order) {
        super(order);
    }

    public Order getOrder(){
        return (Order) getSource();
    }
}
  • 使用事件機(jī)制改變?cè)写a
@Service
public class OrderService {
    //直接注入spring事件發(fā)布者
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    
    /**
     * 簡(jiǎn)單的創(chuàng)建訂單方法
     */
    public void createOrder(Order order) throws InterruptedException {
        System.out.println("創(chuàng)建訂單 order:"+order.getOrderNo()+" 結(jié)束");
        //調(diào)用事件發(fā)布者發(fā)布事件
        applicationEventPublisher.publishEvent(new AfterCreateOrderEvent(order));
        System.out.println("createOrder方法 結(jié)束");
    }

     //加入@EventListener注解后,該方法可以看出一個(gè)事件監(jiān)聽(tīng)者
    @EventListener
    public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
        Order order = afterCreateOrderEvent.getOrder();
        Thread.sleep(2000);
        System.out.println("調(diào)用短信通知服務(wù):" + order.getPhone());
        System.out.println("調(diào)用積分服務(wù)增加貸款積分:"+order.getOrderNo());
    }

    public static void main(String[] args) throws InterruptedException {
        Order order = new Order("N123124124124", "18782202534");
        //這里指定自己的spring配置路徑
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config/spring-config.xml");

        OrderService orderService = context.getBean(OrderService.class);
        orderService.createOrder(order);
    }
}

輸出:

創(chuàng)建訂單 order:N123124124124 結(jié)束
createOrder方法 結(jié)束
調(diào)用短信通知服務(wù):18782202534
調(diào)用積分服務(wù)增加貸款積分:N123124124124

自此,創(chuàng)建訂單與其他操作便實(shí)現(xiàn)了異步和解耦。

另一種異步實(shí)現(xiàn)方式

另外,也可使用@Async注解來(lái)實(shí)現(xiàn)事件的異步調(diào)用

    @EventListener
    @Async
    public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
        Order order = afterCreateOrderEvent.getOrder();
        Thread.sleep(2000);
        System.out.println("調(diào)用短信通知服務(wù):" + order.getPhone());
        System.out.println("調(diào)用積分服務(wù)增加貸款積分:"+order.getOrderNo());
    }

spring配置加上:

    <!--開(kāi)啟異步調(diào)用,并指定線(xiàn)程池--> 
    <task:annotation-driven executor="annotationExecutor" />
    <!--線(xiàn)程池-->
    <task:executor id="annotationExecutor" pool-size="20"/>

但這種方法有弊端,afterCreateOrder()方法不能放在同一類(lèi)(OrderService)里面。原因是spring的代理機(jī)制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,290評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,776評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,510評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,866評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,036評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,331評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,536評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,754評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,154評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,469評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,273評(píng)論 3 399
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,505評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,826評(píng)論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,507評(píng)論 1 92
  • 前言 在微服務(wù)架構(gòu)的系統(tǒng)中,我們通常會(huì)使用輕量級(jí)的消息代理來(lái)構(gòu)建一個(gè)共用的消息主題讓系統(tǒng)中所有微服務(wù)實(shí)例都連接上來(lái)...
    Chandler_玨瑜閱讀 6,602評(píng)論 2 39
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,922評(píng)論 6 342
  • (上一章) 丨全文總目錄丨 神秘消失的情人。 納西族世代流傳的詭異傳說(shuō)。 可怖的黑衣引渡者。 徘徊在麗江的死亡詛咒...
    小巫先生閱讀 418評(píng)論 1 6