【啃啊啃 Spring5 源碼】細碎一:spring 事件機制

閱讀spring源碼時,看到ApplicationEvent相關的代碼覺得熟悉又困惑,深入了解了一下,發現原來是spring事件機制(原諒我之前沒用過……)。
這里在【Spring4揭秘 基礎1】監聽器和事件的基礎下進行一下擴展深入,感謝這篇博文的作者,他的spring基礎系列文章讓我在閱讀源碼時,輕松了不少。

注:源碼部分根據spring-5.0.7版本分析

設計模式

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

觀察者模式簡單可分為兩部分:主題觀察者。當一個主題改變狀態時,它的所有依賴者都會收到通知并進行自動更新。

Spring事件機制簡單可分為三部分:事件廣播觀察者。 “主題改變狀態” 這個動作被抽離成了 一個“事件”,由一個持有所有觀察者的“廣播容器” 進行廣播,“觀察者”們 接收到相應事件后進行自動更新。

這種設計其實繼承自Java本身的事件機制:

  1. java.util.EventObject
    事件狀態對象的基類,它封裝了事件源對象以及和事件相關的信息。所有java的事件類都需要繼承該類。
  2. java.util.EventListener
    觀察者基類,當事件源的屬性或狀態改變的時候,調用相應觀察者內的回調方法。
  3. Source
    主題類,java中未定義,持有所有的觀察者,當主題狀態發生改變,產生事件,負責向所有觀察者發布事件

Java的事件機制這里不敞開講,想了解可看:java 事件機制

Spring中的事件機制

Spring的事件機制相關的核心類有四個:

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

    /**
     * 刪除監聽者(觀察者)
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * 向所有監聽者發布事件
     */
    void multicastEvent(ApplicationEvent event);
}
  • ApplicationListener:觀察者,接收對應事件后,執行邏輯
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    /**
     * 接收事件后,執行相應邏輯
     */
    void onApplicationEvent(E event);
}

事件發布者ApplicationEventPublisher持有廣播,而廣播ApplicationEventMulticaster持有若干觀察者ApplicationListener。一個事件ApplicationEvent可以通過發布者ApplicationEventPublisher發布后,會調用廣播ApplicationEventMulticaster通知所有觀察者,觀察者ApplicationListener收到通知后執行相關操作。

下面舉例說明:
當一個用戶注冊結束后,我們想要將這個事件發生給短信監聽者和郵件監聽者,讓他們向用戶發送短信和郵件。

public class EventDemo {

    public static void main(String[] args) {
        //構建廣播器
        ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        //廣播添加監聽器
        multicaster.addApplicationListener(new RegisterListener1());
        multicaster.addApplicationListener(new RegisterListener2());

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

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

        //發布注冊事件
        eventPublicsher.publishEvent(registerEvent);
    }

    /**
     * 用戶實體類
     */
    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

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

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

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

    /**
     * 注冊事件監聽者2-郵件監聽者(即觀察者),負責注冊后發送郵件
     * 注意:實現接口時,在泛形中指定事件類型,則只監聽該類型事件。若不指定,則默認監聽所有事件。
     */
    public static class RegisterListener2 implements ApplicationListener<RegisterEvent> {

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


    /**
     * 事件發布者,持有監聽者
     */
    public static class MyEventPublicsher implements  ApplicationEventPublisher{
        //廣播
        private ApplicationEventMulticaster eventMulticaster;

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

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

}

輸出:

用戶注冊后
用戶:jack注冊結束,向手機18782252509發送短信!
用戶:jack注冊結束,發生郵件:jack_email@163.com

源碼細節解析

我們主要分析下廣播的細節,以SimpleApplicationEventMulticaster為例:

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

我們可以看到spring廣播時,會先去判斷有沒有配置線程池,如果配置則使用線程池異步執行監聽者邏輯,否則同步。

需要注意的是,我們使用spring事件機制時,默認是沒有配置線程池的,也就是默認所有的通知都是同步的,需要手動指定線程池才會開啟同步。

應用

設計一個業務場景:當一個用戶完成貸款訂單后,我們希望執行發送提醒短信、調用積分服務增加積分、通知風控服務重算風控值(后續操作可能增加)等功能。這種業務需求開始很可能寫成同步代碼。

//創建訂單
public void createOrder(Order order){
    創建貸款訂單;
    發送提醒短信;
    調用積分服務增加積分;
    調用風控服務推送訂單信息;
    ……
    返回;
}

隨著業務復雜度的增加,我們很快發現createOrder()這個方法耦合了太多與創建訂單無關的邏輯,即影響了原本創建訂單方法的效率,在設計上又不符合“開閉原則”。

現在使用spring事件機制我們來解耦,將與注冊無關的操作改為異步。這里直接使用注解式寫法。

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

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

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

    public Order getOrder(){
        return (Order) getSource();
    }
}
  • 使用事件機制改變原有代碼
@Service
public class OrderService {
    //直接注入spring事件發布者
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    
    /**
     * 簡單的創建訂單方法
     */
    public void createOrder(Order order) throws InterruptedException {
        System.out.println("創建訂單 order:"+order.getOrderNo()+" 結束");
        //調用事件發布者發布事件
        applicationEventPublisher.publishEvent(new AfterCreateOrderEvent(order));
        System.out.println("createOrder方法 結束");
    }

     //加入@EventListener注解后,該方法可以看出一個事件監聽者
    @EventListener
    public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
        Order order = afterCreateOrderEvent.getOrder();
        Thread.sleep(2000);
        System.out.println("調用短信通知服務:" + order.getPhone());
        System.out.println("調用積分服務增加貸款積分:"+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);
    }
}

輸出:

創建訂單 order:N123124124124 結束
createOrder方法 結束
調用短信通知服務:18782202534
調用積分服務增加貸款積分:N123124124124

自此,創建訂單與其他操作便實現了異步和解耦。

另一種異步實現方式

另外,也可使用@Async注解來實現事件的異步調用

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

spring配置加上:

    <!--開啟異步調用,并指定線程池--> 
    <task:annotation-driven executor="annotationExecutor" />
    <!--線程池-->
    <task:executor id="annotationExecutor" pool-size="20"/>

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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