前言
本來打算這篇繼續和大家一起討論springboot啟動源碼的,可覺得再講源碼估計大家都沒有看下去的勇氣了,那么今天,我們不講springboot的啟動源碼,我們先來看看一個有趣的內容,具體是什么,大家應該已經知道了,沒錯就是標題中的 – spring-boot事件。
可能有小伙伴覺得奇怪了,好好的源碼系列不講了,怎么突然講一個無關緊要的內容呢?那么真的是無關緊要的內容嗎?關於這個疑問后面會有解答。目前大家就權當放松了,以一種輕松的心態和我一起往下看。
觀察者模式
說好的不是講springboot的事件機制嗎,怎么又講什么觀察者模式呢?心里會說:“樓主,你還好嗎,你今天是不是被門夾了?”。樓主:“被門夾?不存在的,一般只有我夾門......”
還是那就話,大家放松心態慢慢看,心中的疑問先放在心里或者用筆記錄下來,后面會慢慢解開的。
概念其實很簡單,兩個主體,一個觀察者,一個被觀察者,當被觀察者發生變化時,觀察者會有相應的動作。舉幾個例子,和我們日常生活息息相關的紅綠燈,燈就相當於被觀察者,行人就相當於觀察者,當燈發生變化時,行人會有相應的動作:紅燈停,綠燈行,黃燈亮了等一等。再比如我們現在玩的公眾號,當我們訂閱了某個公眾號之后,公眾號每發表一篇文章,就會向訂閱了它的用戶發送這篇文章,我們就可以瀏覽這篇文章了;當我們取消訂閱了,它就不會再向我們推送這篇文章了;只要這個公眾號一直在運行,就會一直有人訂閱它或者取消訂閱。這兩個主體有個統一的稱呼:被觀察者成為主題(Subject),觀察者仍是稱為觀察者(Observer)。
觀察者模式還有很多其他的稱謂,如發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
理論上的東西講的再多也只是停留在理論,下面我們來實現下,到底觀察者模式是個什么神奇的東西。
類圖
所涉及到的角色如下:
抽象主題(Subject):提供接口,可以增加和剔除觀察者對象。一般用抽象類或者接口實現。
抽象觀察者(Observer):提供接口,在得到主題的通知時更新自己。一般用抽象類或者接口實現。
具體主題(ConcreteSubject):將有關狀態存入具體觀察者,在具體主題的內部狀態發生變化時,給所有注冊過的觀察者發出通知。一般是具體子類實現。
具體觀察者(ConcreteObserver):存儲與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用
在上述類圖中,ConcreteSubject中有一個存儲Observer的列表,這意味着ConcreteSubject並不需要知道引用了哪些ConcreteObserver,只要實現(繼承)了Observer的對象都可以存到該列表中。在需要的時候調用Observer的update方法。
一般實現
Subject:

package com.lee.myobserver; /** * 抽象主題 * 提供具體主題需要實現的接口 */ public interface Subject { /** * 注冊觀察者 * @param observer */ void attach(Observer observer); /** * 移除觀察者 * @param observer */ void detach(Observer observer); /** * 通知所有注冊的觀察者 */ void notifyObservers(); }
Observer:

package com.lee.myobserver; /** * 抽象觀察者 * 提供具體觀察者需要實現的接口 */ public interface Observer { /** * */ void update(String state); }
ConcreteSubject:

package com.lee.myobserver.impl; import com.lee.myobserver.Observer; import com.lee.myobserver.Subject; import java.util.ArrayList; import java.util.List; /** * 具體主題 */ public class ConcreteSubject implements Subject { private List<Observer> observerList = new ArrayList<>(); private String state; @Override public void attach(Observer observer) { this.observerList.add(observer); System.out.println("向ConcreteSubject注冊了一個觀察者"); } @Override public void detach(Observer observer) { this.observerList.remove(observer); System.out.println("從ConcreteSubject移除了一個觀察者"); } @Override public void notifyObservers() { this.observerList.forEach(observer -> observer.update(this.state)); } public void changeState(String state) { this.state = state; this.notifyObservers(); } }
ConcreteObserver:

package com.lee.myobserver.impl; import com.lee.myobserver.Observer; /** * 具體觀察者 */ public class ConcreteObserver implements Observer { @Override public void update(String state) { System.out.println("我被通知了,我要改變狀態為:" + state); } }
MyObserverTest:

package com.lee.test; import com.lee.myobserver.Observer; import com.lee.myobserver.impl.ConcreteObserver; import com.lee.myobserver.impl.ConcreteSubject; public class MyObserverTest { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject(); Observer observer = new ConcreteObserver(); subject.attach(observer); subject.changeState("new state"); } }
完整代碼可以從spring-boot-test獲取,在com.lee.myobserver下
運行結果如下:
以上實現中,我們發現ConcreteSubject必須維護一個Observer列表,這會讓人產生疑問:難道每個ConcreteSubject中的List<Observer>會有不同嗎?很明顯,不會,因為List保存的類型是接口類型,那么我們是不是可以把這個維護列表放到Subject中去了?還有我們發現attach、detach、notifyObservers在各個ConcreteSubject的實現都是一樣的,那么我們是不是可以共用起來呢?答案是肯定的!那么我們怎么處理了,只需要將Subject改成抽象類即可,類圖如下,具體實現就交給大家自己了,有了這個類圖,相信大家都可以輕松的完成代碼的實現。
jdk實現
在Java語言的java.util包下,提供了一個Observable類以及一個Observer接口,構成Java語言對觀察者模式的支持。
Observable:

package java.util; /** * 抽象主題,用普通類實現 */ public class Observable { private boolean changed = false; private Vector<Observer> obs; /** * 構建一個含有0個觀察者的主題 */ public Observable() { obs = new Vector<>(); } /** * 注冊某個觀察者到obs中 */ public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } /** * 從obs中移除某個觀察者 */ public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } /** * 相當於notifyObservers(null),具體看下面那個 */ public void notifyObservers() { notifyObservers(null); } /** * 如果本對象有變化,則通知所有注冊了的觀察者,調用他們的update方法 */ public void notifyObservers(Object arg) { /* * a temporary array buffer, used as a snapshot of the state of * current Observers. */ Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } /** * 清空obs */ public synchronized void deleteObservers() { obs.removeAllElements(); } /** * 將changed設置成true,標明本對象發生了變化 */ protected synchronized void setChanged() { changed = true; } /** * 將changed重置成false */ protected synchronized void clearChanged() { changed = false; } /** * 檢測本對象是否發生了變化 */ public synchronized boolean hasChanged() { return changed; } /** * 返回注冊的觀察者數量 */ public synchronized int countObservers() { return obs.size(); } }
Observer:

package java.util; /** * 抽象觀察者,接口實現 */ public interface Observer { /** * 當被觀察者對象發生改變時,此方法被調用 */ void update(Observable o, Object arg); }
Watched:

package com.lee.jdkobserver; import java.util.Observable; /** * 具體主題 */ public class Watched extends Observable { private String data = ""; public void changeData(String data) { if (this.data.equals(data)) { return; } this.data = data; setChanged(); notifyObservers(this.data); } }
Watcher:

package com.lee.jdkobserver; import java.util.Observable; import java.util.Observer; /** * 具體觀察者,實現jdk中的Observer */ public class Watcher implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("數據改變成了:" + arg); } }
JdkObserverTest:

package com.lee.test; import com.lee.jdkobserver.Watched; import com.lee.jdkobserver.Watcher; import java.util.Observer; public class JdkObserverTest { public static void main(String[] args) { Watched watched = new Watched(); Observer observer = new Watcher(); watched.addObserver(observer); watched.changeData("first"); watched.changeData("second"); watched.changeData("third"); watched.changeData("fourth"); } }
完整代碼可以從spring-boot-test獲取,在com.lee.jdkobserver下
運行結果如下
類圖如下
jdk事件
JDK 1.0及更早版本的事件模型基於職責鏈模式,但是這種模型不適用於復雜的系統,因此在JDK 1.1及以后的各個版本中,事件處理模型采用基於觀察者模式的委派事件模型(DelegationEvent Model, DEM),即一個Java組件所引發的事件並不由引發事件的對象自己來負責處理,而是委派給獨立的事件處理對象負責。這並不是說事件模型是基於Observer和Observable的,事件模型與Observer和Observable沒有任何關系,Observer和Observable只是觀察者模式的一種實現而已。
java中的事件機制的參與者有3種角色
Event Eource:事件源,發起事件的主體。
Event Object:事件狀態對象,傳遞的信息載體,就好比Watcher的update方法的參數,可以是事件源本身,一般作為參數存在於listerner 的方法之中。
Event Listener:事件監聽器,當它監聽到event object產生的時候,它就調用相應的方法,進行處理。
其實還有個東西比較重要:事件環境,在這個環境中,可以添加事件監聽器,可以產生事件,可以觸發事件監聽器。
限於篇幅,具體案例實現就不講了,大家可以去spring-boot-test獲取,在com.lee.jdkevent下,里面注釋寫的很詳細了,大家可以好好看看。
spring事件機制
springboot沒有自己的事件機制,用的就是spring的事件機制,這里希望大家別以為和標題不一致。springboot和spring的關系大家可以去捋一捋,這里可以明確的告訴大家,不是對立關系!
spring的事件機制也是從java的事件機制拓展而來,具體往下看
ApplicationEvent:Spring中所有的事件父接口,繼承自java的EventObject

/* * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context; import java.util.EventObject; /** * Class to be extended by all application events. Abstract as it * doesn't make sense for generic events to be published directly. * * @author Rod Johnson * @author Juergen Hoeller */ public abstract class ApplicationEvent extends EventObject { /** use serialVersionUID from Spring 1.2 for interoperability */ private static final long serialVersionUID = 7099057708183571937L; /** System time when the event happened */ private final long timestamp; /** * Create a new ApplicationEvent. * @param source the object on which the event initially occurred (never {@code null}) */ public ApplicationEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } /** * Return the system time in milliseconds when the event happened. */ public final long getTimestamp() { return this.timestamp; } }
ApplicationListener:spring中所有的事件監聽器父接口,繼承自java的EventListener

/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context; import java.util.EventListener; /** * Interface to be implemented by application event listeners. * Based on the standard {@code java.util.EventListener} interface * for the Observer design pattern. * * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type * that it is interested in. When registered with a Spring ApplicationContext, events * will be filtered accordingly, with the listener getting invoked for matching event * objects only. * * @author Rod Johnson * @author Juergen Hoeller * @param <E> the specific ApplicationEvent subclass to listen to * @see org.springframework.context.event.ApplicationEventMulticaster */ @FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }
具體案例
MessageEvent:

package com.lee.springevent.event; import org.springframework.context.ApplicationEvent; /** * 短信事件,事件信息的載體 * 可以從中獲取事件源信息,本例中source不代表事件源 */ public class MessageEvent extends ApplicationEvent { public MessageEvent(Object source) { super(source); } }
MessageListener:

package com.lee.springevent.listener; import com.lee.springevent.event.MessageEvent; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; public class MessageListener implements ApplicationListener{ @Override public void onApplicationEvent(ApplicationEvent event) { if(event instanceof MessageEvent) { String phoneNumber = (String)event.getSource(); System.out.println("我已收到通知:即將向"+phoneNumber+"發短信了..."); } } }
MessageService:

package com.lee.springevent.service; import com.lee.springevent.event.MessageEvent; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** * ApplicationContextAware,能夠獲取spring的上下文環境 * * MessageService相當於之前說的事件環境 */ public class MessageService implements ApplicationContextAware { private ApplicationContext ctx; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.ctx = applicationContext; } public void sendMessage(String phoneNumber) { MessageEvent evt = new MessageEvent(phoneNumber); // 發布事件 ctx.publishEvent(evt); } }
spring-event.xml:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="messageService" class="com.lee.springevent.service.MessageService" /> <bean id="messageListener" class="com.lee.springevent.listener.MessageServiceListener" /> </beans>
SpringEventTest:

package com.lee.test; import com.lee.springevent.service.MessageService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringEventTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-event.xml"); MessageService messageService = (MessageService) applicationContext.getBean("messageService"); messageService.sendMessage("1574480311"); } }
執行SpringEventTest中的main方法就可以看到結果了。
更多詳情請從spring-boot-test獲取,在com.lee.springevent下,大家可以根據注釋好好消化下。實在是不懂得話,可以放一下,下篇博文我會和大家一起着重研究下spring的事件源碼,后續的博文中也都會有提到。
總結
為什么講與springboot啟動源碼無關的內容
關於前言中的疑問:為什么講一篇與spring啟動源碼無關的內容,有兩個考慮,第一,確實是讓大家放松下心態,讀源碼確實挺累的;第二,主要目的,就是為spring-boot-2.0.3啟動源碼篇二 - SpringApplication的run方法(一)之SpringApplicationRunListener做准備,里面會涉及到spring的事件機制
觀察者模式優點與缺點
上面長篇大論,似乎也沒討論其優點與缺點;其實通過實現大家應該才能體會到其優點與缺點,我總結下,有什么不對的大家可以在評論區補充
優點:
(1) 主題與觀察者建立一個抽象的耦合而不是緊密的耦合,降低了耦合度;主題只需要維護一個抽象觀察者的集合,無需了解具體觀察者,使得可以有各種各樣不同的觀察者實現。
(2) 支持廣播通信,主題會向所有已注冊的觀察者對象發送通知,簡化了一對多系統設計的難度。
(3) 符合“開閉原則”,增加新的具體觀察者無須修改原有代碼,可拓展性高。
缺點:
(1) 如果主題有很多直接或者間接觀察者,那么全部通知到會很耗時。
(2) 主題與觀察者之間如果存在循環依賴,可能導致系統崩潰。
觀察者模式應用場景
抽象的來講:對一個對象狀態的更新,需要其他對象同步更新,而且其他對象的數量動態可變;對象僅需要將自己的更新通知給其他對象而不需要知道其他對象的細節。大家可以根據以上兩點作為基本准則,某個場景是否滿足觀察者模式。
具體應用場景就有很多了,比如文中的事件機制、公眾號訂閱,tomcat源碼中也有很多地方用到了(有興趣的兄弟可以去找找)。
事件機制
jdk事件實現是基於觀察者模式,而spring事件又是在jdk事件的基礎上進行了拓展。
主要有四個角色
事件源:觸發事件的主體,比如jdk事件案例中的UserService、spring事件案例中的MessageService、SpringBoot中的SpringApplication。
事件:事件本身,指的是EventObject中的source,具體可以是任何數據(包括事件源),用來傳遞數據,比如jdk事件案例中MessageEvent、spring事件案例中的MessageEvent。
事件監聽器:當事件發生時,負責對事件的處理,比如jdk事件案例中MessageListener、spring事件案例中的MessageListener。
事件環境:整個事件所處的上下文,對整個事件提供支持,類似web上下文;比如jdk事件案例中的UserService、spring事件案例中的ApplicationContext
參考
《Head First 設計模式》
《java與模式》