Springboot事件機制整合EventBus應用(事件驅動模型)

事件監聽機制

熟悉Spring的同學,spring提供了一套完整的事件監聽機制。要了解spring不妨先熟悉一下,觀察者模式。Java 從1.0版本,已經有了觀察者模式的設計。下面通過一個案例來了解一下Java所提供的觀察者模式。

觀察者模式

觀察者模式爲,一個被觀察者Observable(被觀察主題)和多個觀察者Observer,被觀察者中存儲了所有的觀察者對象,當被觀察者接收到一個外界的消息,會通知其所有的觀察者。其實就是一個主題改變,訂閱它的觀察者都會收到相關的消息。(也可以叫做,發佈訂閱模式)
JDK 觀察者模式
JDK 以上是觀察者模式的類圖。 觀察者(Obsever)、被觀察者(Observable)。通過源碼可以看出被觀察者中會維護觀察者對象,觀察者註冊到被觀察者列表後,被觀察者發出的通知觀察者都將收到改變。而觀察者中的unpdate(Observable,Object)方法。就是通過監聽被觀察者發生的改變,來觸發觀察者的響應。
模擬老師和學生的一個場景:被觀察者(老師)、觀察者(學生)。

案例1.

觀察者

public class MyObserver implements Observer {
    /**
     * 觀察者(學生)name
     */
    private String name;
    public MyObserver(Observable o, String name) {
        o.addObserver(this);
        this.name = name;
    }
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("觀察者(學生)" + name + "收到作業!《" + arg + "》"+"目標的觀察者數量=" + o.countObservers());
    }
}

被觀察者

public class MyObserverable extends Observable {
    //被觀察者數據
    private String data;
    public String getData() {
        return data;
    }
    /**
     * 如果有如果改變
     * @param data
     */
    public void setData(String data) {
        if (!this.data.equals(data)) {
            this.data = data;
            //更改變化狀態
            setChanged();
        }
        //通知註冊的觀察者
        notifyObservers(data);
    }
}

測試

public static void main(String[] args) {
        //1.構造被觀察目標。(假如現實場景中的老師)
        MyObserverable observerable = new MyObserverable();
        //2.構造2個觀察者實現類:添加觀察者進觀察目標  (現實場景中的學生,每來一個新學生,要加入老師的觀察者名錄中)
        MyObserver observer1 = new MyObserver(observerable, "tom");
        MyObserver observer2 = new MyObserver(observerable, "jerry");
        //3.被觀察者(老師)發佈今天的作業任務。其註冊的觀察者們(學生們)響應。
        observerable.setData("Java從入門到放棄");
    }

結果:

被觀察者已上線....
觀察者(學生)jerry收到作業!《Java從入門到放棄》目標的觀察者數量=2
觀察者(學生)tom收到作業!《Java從入門到放棄》目標的觀察者數量=2

通過代碼看到,被觀察者的 通知註冊的觀察者 notifyObservers(data);,觸發消息的通知給觀察者們。觀測者觀察到有變化後,做出改變。update(Observable o, Object arg) {}.

Spring事件機制(事件監聽機制)

通過上面的觀察者模式,我們能夠瞭解到,其中的設計模式及思想。監聽者模式有異曲同工之處,監聽者模式包含了一個監聽者Listener與之對應的事件Event,還有一個事件發佈者EventPublish,過程就是EventPublish發佈一個事件,被監聽者捕獲到,然後做出相應的處理。事件監聽機制,其實從JDK 1.1開始有的設計模式,其主要的幾個基類爲 事件源EventObject 、監聽者EventListener、發佈者(Spring)ApplicationEventPublisher
spring事件驅動模型
下面通過案例演示事件發佈訂閱。

案例2.

事件源:
Spring的事件源爲ApplicationEvent,繼承至JDK提供的EventObject 基類。

public class MyContextEvent extends ApplicationEvent {
    public MyContextEvent(Object source) {
        super(source);
        System.out.println("source message->"+source.toString());
    }
}

監聽者:
Spring的監聽者爲ApplicationListener,繼承至JDK提供的EventListener 接口。其實EventListener中沒有任何方法定義,只是作爲監聽者標識。

public class MyContextListener implements ApplicationListener<MyContextEvent> {
    @Override
    public void onApplicationEvent(MyContextEvent myContextEvent) {
        System.out.println("listener this MyContextEvent....");
    }
}

這裏我們通過Spring容器的事件發佈功能來實現,自動以事件的註冊發佈及監聽。
在spring容器事件中AbstractApplicationContext繼承至ConfigurableApplicationContext,
ConfigurableApplicationContext類繼承至ApplicationContext。IOC容器的核心接口ApplicationContext中繼承了,事件發佈ApplicationEventPublisher. 其子類AbstractApplicationContext中實現了父接口ApplicationEventPublisher中的publishEvent(ApplicationEvent event)方法。

//AbstractApplicationContext類中的publishEvent方法。
public void publishEvent(ApplicationEvent event) {
        this.publishEvent(event, (ResolvableType)null);
    }

測試

public static void main(String[] args) {
        //獲取IOC容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //註冊監聽者
        context.register(MyContextListener.class);
        //刷新容器
        context.refresh();
        //事件發佈
        context.publishEvent(new MyContextEvent("publish this event ...."));
    }

結果

source message->publish this event ....
listener this MyContextEvent....

在IOC容器中,添自定義事件的監聽者context.register(MyContextListener.class);
事件發佈通過發佈自定義事件== context.publishEvent(new MyContextEvent(“publish this event …”));==自定義消息。監聽者做出響應。

基於Springboot事件監聽機制整合EventBus應用

前面將觀察者模式以及spring的事件發佈機制。通過案例代碼的方式,介紹了一下。下面介紹一下基於觀察者模式的一個落地實現,Google Guava的EventBus事件處理。EventBus無需實現複雜的事件、監聽者、發佈者。只需要通過提供的API來實現事件機制。Evnet是根據發佈者發佈的事件類型,監聽者訂閱此事件類型,來時間發佈訂閱模式。在使用EventBus時,我們只需將自己要發佈的事件類型通過EventBus API中的public void post(Object event)方法。監聽者通過@Subscribe監聽指定類型的事件。
springboot事件機制也是基於spring事件機制實現。spring boot中支持的事件類型定在org.springframework.boot.context.event包中,springboot的項目啓動加載過程,是通過springboot事件觸發器EventPublishingRunListener來完成。目前支持的事件類型有如下6種。

  1. ApplicationStartedEvent spring boot 啓動監聽類
  2. ApplicationEnvironmentPreparedEvent 環境事先準備,spring boot中的環境已經準備ok
  3. ApplicationPreparedEvent 上下文準備事件
  4. ApplicationReadyEvent 上下文已經準備ok
  5. ApplicationFailedEvent 該事件爲spring boot啓動失敗時的操作(啓動時出現異常事件)
  6. SpringApplicationEvent 獲取SpringApplication
    每一步加載過程通過觸發不同的事件,來完成相應的操作。這裏不一一贅述,springboot的事件。感興趣的同學可以通過翻看啓動類SpringApplication.run(CommonApplication.class, args)SpringApplication類中的run方法來研究整個啓動過程,做了哪些工作,網上的這部分的講解也是一大堆,貼一張springboot的啓動流程。
    springboot啓動流程
    EventBus API的調用實現起來並不複雜,下面通過一個實際項目中的運用,結合springboot的事件發佈機制,來整合基於事件驅動的項目案例。首先要清楚前面所將的Spring的事件發佈機制,通過Spring的事件發佈機制,整合EventBus。

思想思路:
1.通過Springboot的事件加載機制,監聽ApplicationPreparedEvent上下文準備事件,在上下文準備事件時,將監聽這一事件的監聽者(實現spring中的ApplicationListener),添加至EventBus的事件監聽者中(即 Event的監聽者註冊register(Object handler))。
2.添加訂閱者(即實現ApplicationListener監聽者)的監聽者。通過註解@Subscrib實現監聽。
3.事件發佈。調用EventBus中的事件發佈API post(Object event).發佈事件。
4.訂閱者響應。

案例3.

Maven 引用相應Jar包
EventBus 事件總線

//api封裝
public class MyEventBus {

    /** 事件任務總線 */
    private final static EventBus tiemEventBus = new EventBus();
    /**
     * 觸發同步事件
     *
     * @param event
     */
    public static void post(Object event) {
        tiemEventBus.post(event);
    }
    /**
     * 註冊事件處理器
     *
     * @param handler
     */
    public static void register(Object handler) {
        tiemEventBus.register(handler);
    }
    /**
     * 註銷事件處理器
     *
     * @param handler
     */
    public static void unregister(Object handler) {
        tiemEventBus.unregister(handler);
    }
}

事件監聽者
監聽ApplicationPreparedEvent上下文準備事件,即springboot加載至這一步時,將此監聽者註冊到EventBus中。通過抽象封裝的方式,後繼承的子類通過實現此類,實現訂閱者的eventBus註冊。

@Component
abstract class MyApplicationListener implements ApplicationListener<ApplicationPreparedEvent> {
    /**
     *  ApplicationPreparedEvent 上下文準備事件
     * @param applicationPreparedEvent
     */
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) {
        ConfigurableApplicationContext applicationContext = applicationPreparedEvent.getApplicationContext();
        MyApplicationListener bean = applicationContext.getBean(this.getClass());
        System.out.println("regist listener to eventBus...."+bean);
        MyEventBus.register(bean);
    }
}

訂閱者(也即監聽者)繼承至MyApplicationListener。

@Component
public class MyListentenerSubscribe extends MyApplicationListener{
    @Subscribe
    public void on(Message message){
        System.out.println("subscribe message->  messgeType:"+message.getMessageType()+"\n messageContent:"+message.getMessageContent());
    }
}

測試
這裏因爲是案例實現,直接在Controller實現時間的發佈。真實的業務中,通業務層,具體業務觸發的事件的發佈。
我通過發佈User

@RestController
public class EventPublishCtrl extends LogBase {
    @GetMapping("/publish")
    public void publishEvent() {
        log.info("this publish method...");
        MyEventBus.post(new Message(Message.MessageType.OPENDOOR,"芝麻開門!"));
    }
}

啓動輸出

2019-08-22 14:39:15.811 WARN 73026 — [ main] com.netflix.discovery.DiscoveryClient : Using default backup registry implementation which does not do anything.
2019-08-22 14:39:15.813 INFO 73026 — [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
2019-08-22 14:39:15.814 INFO 73026 — [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
2019-08-22 14:39:15.818 INFO 73026 — [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1566455955817 with initial instances count: 0
2019-08-22 14:39:15.820 INFO 73026 — [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application UNKNOWN with eureka with status UP
2019-08-22 14:39:15.820 INFO 73026 — [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1566455955820, current=UP, previous=STARTING]
2019-08-22 14:39:15.822 INFO 73026 — [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/kafka1:9090: registering service…
regist listener to eventBus....com.uniubi.springcloud.practice.eventBus.MyListentenerSubscribe@329a1f8d
2019-08-22 14:39:15.914 INFO 73026 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path ‘’
2019-08-22 14:39:15.915 INFO 73026 — [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 9090
2019-08-22 14:39:15.917 INFO 73026 — [ main] c.u.s.practice.PracticeApplication : Started PracticeApplication in 4.101 seconds (JVM running for 4.895)
2019-08-22 14:39:16.761 INFO 73026 — [-192.168.63.121] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet ‘dispatcherServlet’

項目的啓動加載,註冊相應的監聽者至eventBus中。
測試事件發佈

2019-08-22 14:43:44.399  INFO 73026 --- [nio-9090-exec-1] c.u.springcloud.practice.common.LogBase  : this publish method...
subscribe message->  messgeType:OPENDOOR
 messageContent:芝麻開門!

訂閱者收到具體的消息類型,以及消息內容。
總結

springBoot的底層實現,很多都是基於事件的發佈訂閱模式來做的。我們日常的開發過程中,做到業務的解耦,消息的異步傳輸。都可以通過實現發佈訂閱的模式來實現。這裏也是實現項目中基於事件驅動,來完成核心業務的解耦。

  • 文中的圖片來自網絡。如有侵權行爲聯繫作者。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章