原理及應用場景
觀察者模式(Observer Design Pattern)也被稱爲發佈訂閱模式(Publish-Subscribe Design Pattern)。在GoF的《設計模式》一書中,它的定義是這樣的:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
翻譯成中文就是:在對象之間定義一個一對多的依賴,當一個對象狀態改變的時候,所有依賴的對象都會自動收到通知。一般情況下,被依賴的對象叫作被觀察者(Observable),依賴的對象叫作觀察者(Observer)。Observer模式是比較常用的設計模式之一,雖然有時候在具體代碼裏,它不一定叫這個名字,比如改頭換面叫個Listener,但模式就是這個模式。各種不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。不管怎麼稱呼,只要應用場景符合剛剛給出的定義,都可以看作觀察者模式。
觀察者模式是一個比較抽象的模式,根據不同的應用場景和需求,有完全不同的實現方式,先來看其中最經典的一種實現方式。這也是在講到這種模式的時候,很多書籍或資料給出的最常見的實現方式。具體的代碼如下所示,先定義兩個觀察者與被觀察者接口,然後實現之。
//被觀察者接口
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}
//觀察者接口
public interface Observer {
void handle(Message message);
}
實現接口,定義具體的觀察者與被觀察者。
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<Observer>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Message message) {
for (Observer observer : observers) {
observer.handle(message);
}
}
}
public class ConcreteObserverOne implements Observer {
@Override
public void handle(Message message) {
//TODO: 獲取消息通知,執行自己的邏輯...
System.out.println("ConcreteObserverOne is notified.");
}
}
public class ConcreteObserverTwo implements Observer {
@Override
public void handle(Message message) {
//TODO: 獲取消息通知,執行自己的邏輯...
System.out.println("ConcreteObserverTwo is notified.");
}
}
測試代碼
public class Demo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.registerObserver(new ConcreteObserverOne());
subject.registerObserver(new ConcreteObserverTwo());
subject.notifyObservers(new Message());
}
}
上面的代碼算是觀察者模式的“模板代碼”,只能反映大體的設計思路。在真實的軟件開發中,並不需要照搬上面的模板代碼。觀察者模式的實現方法各式各樣,函數、類的命名等會根據業務場景的不同有很大的差別,比如 register函數還可以叫作attach,remove函數還可以叫作detach等等。不過,萬變不離其宗,設計思路都是差不多的。
觀察者模式的應用場景非常廣泛,小到代碼層面的解耦,大到架構層面的系統解耦,再或者一些產品的設計思路,都有這種模式的影子,比如,郵件訂閱、RSS Feeds,本質上都是觀察者模式。不同的應用場景和需求下,這個模式也有截然不同的實現方式,有同步阻塞的實現方式,也有異步非阻塞的實現方式;有進程內的實現方式,也有跨進程的實現方式(消息隊列)。從剛剛的分類方式上來看,示例代碼中的實現方式是一種同步阻塞的實現方式。觀察者和被觀察者代碼在同一個線程內執行,被觀察者一直阻塞,直到所有的觀察者代碼都執行完成之後,才執行後續的代碼。
異步非阻塞的觀察者模式
對於異步非阻塞觀察者模式,如果只是實現一個簡易版本,不考慮任何通用性、複用性,實際上是非常容易的。我們有兩種實現方式。其中一種是:在每個Observer的handle() 函數中創建一個新的線程執行代碼邏輯;另一種是:在ConcreteSubject的notifyObservers()函數中使用單獨的線程池來執行每個觀察者的handle() 函數,兩種實現方式的具體代碼如下所示。
第一種實現方式,在Observer的handle()函數中起一個新線程執行處理邏輯:
public class ConcreteObserverOne implements Observer {
@Override
public void handle(Message message) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//TODO: 獲取消息通知,執行自己的邏輯...
System.out.println("ConcreteObserverOne is notified.");
}
});
thread.start();
}
}
public class ConcreteObserverTwo implements Observer {
@Override
public void handle(Message message) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//TODO: 獲取消息通知,執行自己的邏輯...
System.out.println("ConcreteObserverTwo is notified.");
}
});
thread.start();
}
}
第二種實現方式,在Subject的notifyObservers()函數中起一個線程池去執行所有觀察者的處理邏輯:
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<Observer>();
//使用單獨的線程池
private Executor executor;
public ConcreteSubject(Executor executor) {
this.executor = executor;
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
//使用單獨的線程池去運行各個觀察者的處理代碼
@Override
public void notifyObservers(Message message) {
for (Observer observer : observers) {
executor.execute(new Runnable() {
@Override
public void run() {
observer.handle(message);
}
})
}
}
}
對於第一種實現方式,頻繁地創建和銷燬線程比較耗時,並且併發線程數無法控制,創建過多的線程會導致堆棧溢出。第二種實現方式,儘管利用了線程池解決了第一種實現方式的問題,但線程池、異步執行邏輯都耦合在了notifyObservers() 函數中,增加了這部分業務代碼的維護成本。如果在項目中,不止一個業務模塊需要用到異步非阻塞的觀察者模式,這樣的代碼實現也無法做到複用。我們知道,框架的作用有:隱藏實現細節,降低開發難度,做到代碼複用,解耦業務與非業務代碼,讓程序員聚焦業務開發。針對異步非阻塞觀察者模式,我們也可以將它抽象成框架來達到這樣的效果,而這個框架就是EventBus。
EventBus框架
EventBus翻譯爲“事件總線”,它提供了實現觀察者模式的骨架代碼。我們可以基於此框架,非常容易地在自己的業務場景中實現觀察者模式,不需要從零開始開發。其中,Google Guava EventBus就是一個比較著名的 EventBus框架,它不僅僅支持異步非阻塞模式,同時也支持同步阻塞模式。guava包中的EventBus是一個事件通知組件,可以用來在同一個JVM中,實現事件通知機制,這裏和Android的EventBus不是一個事物。在一些業務場景中,我們會使用Redis隊列或者Kafka等其他MQ來實現分佈式多進程間的消息通知,而EventBus是在單個JVM中使用。可以對代碼邏輯進行相應的解耦,某些場景下,比如異步調用,可以提高整體的運行性能。
EventBus實際上是一個消息隊列,Event Source發送一個消息到EventBus,然後再由EventBus將消息推送到所監聽的Listener。
EventBus包含兩個大的模塊:發佈器與訂閱器。
發佈器主要是兩個方法:
- eventBus.register(Object o),用來註冊監聽器,傳入監聽器類型。細節是將監聽器所有@Subscribe註解的方法和事件類型放置到一個map裏。
- eventBus.post(Object o),顧名思義就是將消息事件發送出去,最後交給有@Subscribe註解的方法去處理。多個方法可以同時處理一個消息,可以理解爲廣播。
訂閱器就比較簡單,只有一個@Subscribe的註解,在收到對應的消息類型之後就可以執行方法中的邏輯。
下面基於Spring容器構建一個簡單的EvenBus應用示例。
@Configuration
public class EventBusConfig {
//創建一個異步非阻塞的eventBus,交給Spring容器管理
@Bean(name = "eventBus")
public EventBus eventBus() {
// 直接交接隊列(SynchronousQueue):任務不多時,只需要用隊列進行簡單的任務中轉,
// 這種隊列無法存儲任務,在使用這種隊列時,需要將maxPoolSize設置的大一點。
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ThreadPoolExecutor executor = new ThreadPoolExecutor
(2, 10, 50, TimeUnit.MILLISECONDS,
new SynchronousQueue<>(), namedThreadFactory,
new ThreadPoolExecutor.DiscardPolicy());
//使用guava包提供的異步非阻塞eventBus,構造函數中必須傳入線程池
return new AsyncEventBus(executor);
}
}
定義被觀察者類
@Component
public class Subject {
//容器作依賴注入
@Resource
private EventBus eventBus;
//發送事件
public void eventPost(){
String[] strings = new String[]{"A","B"};
for (String str : strings) {
eventBus.post(new Message(str, RandomUtils.nextLong()));
}
}
}
定義觀察者類
@Component
public class Observer {
//容器作依賴注入
@Resource
private EventBus eventBus;
//觀察者註冊自己
@PostConstruct
public void init() {
eventBus.register(this);
}
/**
* 只監聽Message類型及其子類的消息
* @param message
*/
@Subscribe
public void listenEventA(Message message) {
System.out.println("listenEventA, " + message.toString());
}
/**
* 監聽所有類型的消息,因爲所有類都是Object類的子類
* @param object
*/
@Subscribe
public void listenEventB(Object object) {
System.out.println("listenEventB, " + object.toString());
}
}
定義消息類型
public class Message {
private String text;
private Long id;
public Message(String text, Long id) {
this.text = text;
this.id = id;
}
@Override
public String toString() {
return "Message{" +
"text='" + text + '\'' +
", id=" + id +
'}';
}
}
單元測試
public class EventBusTest extends ApplicationTest {
//容器作依賴注入
@Resource
private Subject subject;
//容器作依賴注入
@Resource
private Observer observer;
@Test
public void testObserver() {
System.out.println(observer.getClass().getName());
subject.eventPost();
}
}
測試結果如下:
跟經典的觀察者模式的不同之處在於,當我們調用post()函數發送消息的時候,並非把消息發送給所有的觀察者,而是發送給可匹配的觀察者。所謂可匹配指的是,能接收的消息類型是發送消息類型的父類。
每個Observer能接收的消息類型是在哪裏定義的呢? 這是Guava EventBus最特別的一個地方,@Subscribe註解。EventBus通過 @Subscribe註解來標明,某個函數能接收哪種類型的消息。實現EventBus框架最關鍵的一個數據結構是Observer註冊表,該註冊表記錄了消息類型和可接收消息函數的對應關係。當調用register()函數註冊觀察者的時候,EventBus通過解析@Subscribe註解,生成Observer註冊表。當調用post()函數發送消息的時候,EventBus通過註冊表找到相應的可接收消息的函數,然後通過Java的反射語法來動態地創建對象、執行函數。對於同步阻塞模式,EventBus在一個線程內依次執行相應的函數。對於異步非阻塞模式,EventBus通過一個線程池來執行相應的函數。
EventBus的觀察者註冊源碼閱讀
EventBus類核心代碼如下:
public class EventBus {
private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
/**
* Creates a new EventBus named "default".
*/
public EventBus() {
this("default");
}
/**
* Creates a new EventBus with the given {@code identifier}.
*
* @param identifier a brief name for this bus, for logging purposes. Should
* be a valid Java identifier.
*/
public EventBus(String identifier) {
this(identifier, MoreExecutors.directExecutor(),
Dispatcher.perThreadDispatchQueue(), LoggingHandler.INSTANCE);
}
//subscribers來管理觀察者的註冊與取消註冊
public void register(Object object) {
subscribers.register(object);
}
public void unregister(Object object) {
subscribers.unregister(object);
}
//其他代碼省略
}
MoreExecutors.directExecutor()是Google Guava提供的工具類,看似是多線程,實際上是單線程。之所以要這麼實現,主要還是爲了跟 AsyncEventBus統一代碼邏輯,做到代碼複用。來看SubscriberRegistry類的核心代碼。
final class SubscriberRegistry {
/**
* All registered subscribers, indexed by event type.
* 以事件類型爲key,多個觀察者實體組成的set爲value,建立map
* 這個map就是觀察者註冊表
*/
private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers =
Maps.newConcurrentMap();
/**
* The event bus this registry belongs to.
*/
private final EventBus bus;
SubscriberRegistry(EventBus bus) {
this.bus = checkNotNull(bus);
}
/**
* 將一個觀察者加入觀察者註冊表
* Registers all subscriber methods on the given listener object.
*/
void register(Object listener) {
Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
for (Map.Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
Class<?> eventType = entry.getKey();
Collection<Subscriber> eventMethodsInListener = entry.getValue();
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
if (eventSubscribers == null) {
CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<Subscriber>();
eventSubscribers = MoreObjects.firstNonNull(
subscribers.putIfAbsent(eventType, newSet), newSet);
}
eventSubscribers.addAll(eventMethodsInListener);
}
}
/**
* 找到一個觀察者所訂閱的所有事件,因爲一個類可以有多個方法
* @Subsrcribe可以註解多個方法,同一個事件也可以被一個類的多個方法訂閱
* 因此最終返回的是一個多值map,以事件類型爲key
* Returns all subscribers for the given listener grouped by the type of event they subscribe to.
*/
private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
//遍歷所有被@Subsrcribe註解修飾的方法
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
}
return methodsInListener;
}
//其他代碼省略
}
subscribers就是觀察者註冊表,其中使用到了CopyOnWriteArraySet,CopyOnWriteArraySet基於寫時複製思想,在寫入數據的時候,會創建一個新的set,並且將原始數據clone到新的set中,在新的set中寫入數據完成之後,再用新的set替換老的set。這樣就能保證在寫入數據的時候,不影響數據的讀取操作,以此來解決讀寫併發問題。除此之外,CopyOnWriteSet還通過加鎖的方式,避免了併發寫衝突。
觀察者註冊表中的Subscriber類用來表示@Subscribe註解的方法,該類的代碼如下,其中,target表示觀察者實體,method表示方法。
class Subscriber {
/** The event bus this subscriber belongs to. */
private EventBus bus;
/** Object sporting the subscriber method. */
@VisibleForTesting
final Object target;
/** Subscriber method. */
private final Method method;
/** Executor to use for dispatching events to this subscriber. */
private final Executor executor;
private Subscriber(EventBus bus, Object target, Method method) {
this.bus = bus;
this.target = checkNotNull(target);
this.method = method;
method.setAccessible(true);
this.executor = bus.executor();
}
//其他代碼省略
}
自定義簡易EventBus框架
讀完以上源碼,Guava EventBus 的核心原理也就弄清楚了。接下來,自己參考源碼“山寨”一個簡單的EventBus出來。
首先定義@Subrcibe註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {
}
定義ObserverAction類用來表示@Subscribe註解的方法,其中,target表示觀察者實體,method表示方法。它主要用在觀察者註冊表中。
public class ObserverAction {
private Object target;
private Method method;
public ObserverAction(Object target, Method method) {
this.target = target;
this.method = method;
this.method.setAccessible(true);
}
/**
* 反射執行觀察者的方法
* @param event
*/
public void execute(Object event) {
try {
method.invoke(target, event);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || this.getClass() != object.getClass()) {
return false;
}
ObserverAction that = (ObserverAction)object;
return Objects.equals(target, that.target) && Objects.equals(method, that.method);
}
@Override
public int hashCode() {
return Objects.hash(target, method);
}
}
ObserverRegistry類就是Observer註冊表,是最複雜的一個類,框架中幾乎所有的核心邏輯都在這個類中。這個類大量使用了Java的反射語法。
public class ObserverRegistry {
private ConcurrentHashMap<Class<?>, CopyOnWriteArraySet<ObserverAction>> registry = new ConcurrentHashMap<>();
/**
* 註冊一個觀察者
* @param observer
*/
public void register(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActionMap = findAllObserverActions(observer);
if (MapUtils.isEmpty(observerActionMap)) {
return;
}
for (Map.Entry<Class<?>, Collection<ObserverAction>> entry : observerActionMap.entrySet()) {
Class<?> eventType = entry.getKey();
Collection<ObserverAction> observerActions = entry.getValue();
CopyOnWriteArraySet<ObserverAction> observerActionSet = registry.get(eventType);
if (observerActionSet == null) {
registry.put(eventType, new CopyOnWriteArraySet<>(observerActions));
} else {
observerActionSet.addAll(observerActions);
}
}
}
/**
* 取消一個觀察者的註冊信息
* @param observer
*/
public void unregister(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActionMap = findAllObserverActions(observer);
if (MapUtils.isEmpty(observerActionMap)) {
return;
}
for (Map.Entry<Class<?>, Collection<ObserverAction>> entry : observerActionMap.entrySet()) {
Class<?> eventType = entry.getKey();
Collection<ObserverAction> observerActions = entry.getValue();
CopyOnWriteArraySet<ObserverAction> observerActionSet = registry.get(eventType);
if (observerActionSet == null) {
continue;
}
observerActionSet.removeAll(observerActions);
}
}
/**
* 獲取某個具體事件的所有觀察者,可以有多個方法訂閱一個事件
* @param event
* @return
*/
public List<ObserverAction> getMatchedObserverActions(Object event) {
List<ObserverAction> matchedObserverActions = new ArrayList<>();
Class<?> postedEvenType = event.getClass();
for (Map.Entry<Class<?>, CopyOnWriteArraySet<ObserverAction>> entry : registry.entrySet()) {
if (entry.getKey().isAssignableFrom(postedEvenType)) {
matchedObserverActions.addAll(entry.getValue());
}
}
return matchedObserverActions;
}
/**
* 獲取一個觀察者實體訂閱的所有事件及其處理方法的映射
* key是觀察者方法訂閱的事件類型,即post(Object o)方法中入參o的類型
* @param observer
* @return
*/
private Map<Class<?>, Collection<ObserverAction>> findAllObserverActions(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActionMap = new HashMap<>();
Class<?> clazz = observer.getClass();
List<Method> subscribeAnnotatedMethods = getSubscribeAnnotatedMethods(clazz);
if (CollectionUtils.isEmpty(subscribeAnnotatedMethods)) {
return observerActionMap;
}
for (Method method : subscribeAnnotatedMethods) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> subscribeEventType = parameterTypes[0];
if (!observerActionMap.containsKey(subscribeEventType)) {
observerActionMap.put(subscribeEventType, new ArrayList<>());
}
observerActionMap.get(subscribeEventType).add(new ObserverAction(observer, method));
}
return observerActionMap;
}
/**
* 獲取一個類的所有訂閱了事件的方法,即所有@Subscribe註解過的方法
* @param clazz
* @return
*/
private List<Method> getSubscribeAnnotatedMethods(Class<?> clazz) {
List<Method> subscribeAnnotatedMethods = new ArrayList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Preconditions.checkArgument(parameterTypes.length == 1,
"Method %s has @Subscribe annotation but has %s parameters.Subscriber methods must have exactly 1 parameter.",
method, parameterTypes.length);
}
subscribeAnnotatedMethods.add(method);
}
return subscribeAnnotatedMethods;
}
}
定義EventBus類,實現的是阻塞同步的觀察者模式。
public class EventBus {
private Executor executor;
private ObserverRegistry observerRegistry = new ObserverRegistry();
public EventBus() {
this(MoreExecutors.directExecutor());
}
public EventBus(Executor executor) {
this.executor = executor;
}
public void register(Object object) {
observerRegistry.register(object);
}
public void unregister(Object object) {
observerRegistry.unregister(object);
}
public void post(Object event) {
List<ObserverAction> matchedObserverActions = observerRegistry.getMatchedObserverActions(event);
for (ObserverAction observerAction : matchedObserverActions) {
executor.execute(() -> observerAction.execute(event));
}
}
}
定義EventBus管理器類。
public class EventBusManager {
private EventBus eventBus;
private EventBusManager() {
this(new EventBus());
}
private EventBusManager(EventBus eventBus) {
this.eventBus = eventBus;
}
/**
* 單例模式
*/
public static class EventBusManagerHolder {
public static final EventBusManager instance = new EventBusManager();
public static EventBusManager getInstance() {
return EventBusManagerHolder.instance;
}
}
public void register(Object object) {
eventBus.register(object);
}
public void unregister(Object object) {
eventBus.unregister(object);
}
public void post(Object event) {
eventBus.post(event);
}
}
定義觀察者類。
public class Observer {
@Subscribe
public void stringOut(String t) {
System.out.println("stringOut Subscribe event, event = " + t);
}
@Subscribe
public void integerOut(Integer t) {
System.out.println("integerOut Subscribe Integer event, event = " + t);
}
}
測試代碼
public class CustomEventBusTest {
public static void main(String[] args) {
Observer observer = new Observer();
EventBusManagerHolder.getInstance().register(observer);
EventBusManagerHolder.getInstance().post("直接測試字符串");
EventBusManagerHolder.getInstance().post(123);
EventBusManagerHolder.getInstance().unregister(observer);
EventBusManagerHolder.getInstance().post("直接測試字符串");
EventBusManagerHolder.getInstance().post(123);
}
}
測試結果