事件監聽機制
熟悉Spring的同學,spring提供了一套完整的事件監聽機制。要了解spring不妨先熟悉一下,觀察者模式。Java 從1.0版本,已經有了觀察者模式的設計。下面通過一個案例來了解一下Java所提供的觀察者模式。
觀察者模式
觀察者模式爲,一個被觀察者Observable(被觀察主題)和多個觀察者Observer,被觀察者中存儲了所有的觀察者對象,當被觀察者接收到一個外界的消息,會通知其所有的觀察者。其實就是一個主題改變,訂閱它的觀察者都會收到相關的消息。(也可以叫做,發佈訂閱模式)
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
下面通過案例演示事件發佈訂閱。
案例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種。
- ApplicationStartedEvent spring boot 啓動監聽類
- ApplicationEnvironmentPreparedEvent 環境事先準備,spring boot中的環境已經準備ok
- ApplicationPreparedEvent 上下文準備事件
- ApplicationReadyEvent 上下文已經準備ok
- ApplicationFailedEvent 該事件爲spring boot啓動失敗時的操作(啓動時出現異常事件)
- SpringApplicationEvent 獲取SpringApplication
每一步加載過程通過觸發不同的事件,來完成相應的操作。這裏不一一贅述,springboot的事件。感興趣的同學可以通過翻看啓動類SpringApplication.run(CommonApplication.class, args)
。SpringApplication
類中的run
方法來研究整個啓動過程,做了哪些工作,網上的這部分的講解也是一大堆,貼一張springboot的啓動流程。
EventBus API的調用實現起來並不複雜,下面通過一個實際項目中的運用,結合springboot的事件發佈機制,來整合基於事件驅動的項目案例。首先要清楚前面所將的Spring的事件發佈機制,通過Spring的事件發佈機制,整合EventBus。
思想思路:
1.通過Springboot的事件加載機制,監聽ApplicationPreparedEvent上下文準備事件,在上下文準備事件時,將監聽這一事件的監聽者(實現spring中的ApplicationListener),添加至EventBus的事件監聽者中(即 Event的監聽者註冊register(Object handler)
)。
2.添加訂閱者(即實現ApplicationListener監聽者)的監聽者。通過註解@Subscrib
實現監聽。
3.事件發佈。調用EventBus中的事件發佈APIpost(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的底層實現,很多都是基於事件的發佈訂閱模式來做的。我們日常的開發過程中,做到業務的解耦,消息的異步傳輸。都可以通過實現發佈訂閱的模式來實現。這裏也是實現項目中基於事件驅動,來完成核心業務的解耦。
- 文中的圖片來自網絡。如有侵權行爲聯繫作者。