觀察者模式及EventBus框架簡單實現

原理及應用場景

觀察者模式(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包含兩個大的模塊:發佈器與訂閱器。
發佈器主要是兩個方法:

  1. eventBus.register(Object o),用來註冊監聽器,傳入監聽器類型。細節是將監聽器所有@Subscribe註解的方法和事件類型放置到一個map裏。
  2. 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);
    }
}

測試結果
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章