观察者模式(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)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式
使用方式非常简单,Example:
public class EventBusExample {
private static final String EVENT = "Hello";
private EventBus eventBus = new EventBus();
@Test
public void testEventBus() {
StringCatcher catcher = new StringCatcher();
// 注册一个观察者对象, 其中@Subscribe标记的方法,第一个参数类型即作为被监听的事件
eventBus.register(catcher);
// 提交一个事件, 根据event类型,从subscribe映射表中找出所有@Subscribe的订阅者,
// 然后所有订阅者都处理这些事件
eventBus.post(EVENT);
assertEquals(1, catcher.events.size());
assertEquals(EVENT, catcher.events.get(0));
}
public static class StringCatcher {
private List events = Lists.newArrayList();
@Subscribe
public void hereHaveAString(@Nullable String string) {
events.add(string);
}
public void methodWithoutAnnotation(@Nullable String string) {
fail("Event bus must not call methods without @Subscribe!");
}
public List getEvents() {
return events;
}
}
}
实现原理:
核心组件有
这里有个小技巧,subscribers映射表使用了CopyOnWriteArraySet集合,在多线程的情况下,写时复制,也就是在写入数据的时候,
会创建一个新的 set,并且将原始数据 clone 到新的 set 中,在新的 set 中写入数据完成之后,再用新的 set 替换老的 set。这样就能保证在写入数据的时候,不影响数据的读取操作,以此来解决读写并发问题
// SubscriberRegistry#subscribers参数
// private final ConcurrentMap, CopyOnWriteArraySet> subscribers =
Maps.newConcurrentMap(); // 整个注册和事件处理都是基于此注册表进行处理
void register(Object listener) {
// 找到该listener的事件和Subscriber映射关系
// <事件类型, Subscriber>注册表
Multimap, Subscriber> listenerMethods = findAllSubscribers(listener);
for (Entry, Collection> entry : listenerMethods.asMap().entrySet()) {
Class> eventType = entry.getKey();
Collection eventMethodsInListener = entry.getValue();
CopyOnWriteArraySet eventSubscribers = subscribers.get(eventType);
if (eventSubscribers == null) {
CopyOnWriteArraySet newSet = new CopyOnWriteArraySet<>();
eventSubscribers =
MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
}
// 将注册信息维护到subscribers表中
eventSubscribers.addAll(eventMethodsInListener);
}
}
/** Unregisters all subscribers on the given listener object. */
void unregister(Object listener) {
Multimap, Subscriber> listenerMethods = findAllSubscribers(listener);
// 和注册过程相反,从subscribers表中删除该listener的Subscriber相关信息
for (Entry, Collection> entry : listenerMethods.asMap().entrySet()) {
Class> eventType = entry.getKey();
Collection listenerMethodsForType = entry.getValue();
CopyOnWriteArraySet currentSubscribers = subscribers.get(eventType);
if (currentSubscribers == null || !currentSubscribers.removeAll(listenerMethodsForType)) {
// if removeAll returns true, all we really know is that at least one subscriber was
// removed... however, barring something very strange we can assume that if at least one
// subscriber was removed, all subscribers on listener for that event type were... after
// all, the definition of subscribers on a particular class is totally static
throw new IllegalArgumentException(
"missing event subscriber for an annotated method. Is " + listener + " registered?");
}
// don't try to remove the set if it's empty; that can't be done safely without a lock
// anyway, if the set is empty it'll just be wrapping an array of length 0
}
}
找出指定listener中的订阅者信息(@Subscribe注解的方法)
private Multimap, Subscriber> findAllSubscribers(Object listener) {
// @Subscribe注解的方法,第一个参数表示可以响应的事件类型
// 因此这里的映射关系是
Multimap, Subscriber> methodsInListener = HashMultimap.create();
Class> clazz = listener.getClass();
// 这里就是通过反射拿到有@Subscriber注解的方法
for (Method method : getAnnotatedMethods(clazz)) {
Class>[] parameterTypes = method.getParameterTypes();
Class> eventType = parameterTypes[0];
methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
}
return methodsInListener;
}
这里有个小技巧,在通过反射拿到有@Subscriber注解的方法时会用一个缓存,来保存
比如创建多个EventBus实例 注册相同的listener时,就不用在通过反射拿到这种对应关系,提高效率
private static ImmutableList getAnnotatedMethods(Class> clazz) {
return subscriberMethodsCache.getUnchecked(clazz);
}
/**
* A thread-safe cache that contains the mapping from each class to all methods in that class and
* all super-classes, that are annotated with {@code @Subscribe}. The cache is shared across all
* instances of this class; this greatly improves performance if multiple EventBus instances are
* created and objects of the same class are registered on all of them.
*/
private static final LoadingCache, ImmutableList> subscriberMethodsCache =
CacheBuilder.newBuilder()
// 这里key用的弱引用,也就是每次GC不管内存是否足够都会回收掉,
// key被回收掉,相应的记录也就会被删除,下次取数据时需要重新加载
.weakKeys()
.build(
new CacheLoader, ImmutableList>() {
@Override
public ImmutableList load(Class> concreteClass) throws Exception {
return getAnnotatedMethodsNotCached(concreteClass);
}
});
private static ImmutableList getAnnotatedMethodsNotCached(Class> clazz) {
Set extends Class>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
Map identifiers = Maps.newHashMap();
for (Class> supertype : supertypes) {
for (Method method : supertype.getDeclaredMethods()) {
// 这里就是要找到带有@Subscribe注解的方法
if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
// TODO(cgdecker): Should check for a generic parameter type and error out
Class>[] parameterTypes = method.getParameterTypes();
checkArgument(
parameterTypes.length == 1,
"Method %s has @Subscribe annotation but has %s parameters."
+ "Subscriber methods must have exactly 1 parameter.",
method,
parameterTypes.length);
MethodIdentifier ident = new MethodIdentifier(method);
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, method);
}
}
}
}
return ImmutableList.copyOf(identifiers.values());
}
从subscribers表中找到指定事件的订阅者集合
Iterator getSubscribers(Object event) {
// 先把事件类型平铺开(父类等信息),当然这里也用的是缓存来优化
ImmutableSet> eventTypes = flattenHierarchy(event.getClass());
List> subscriberIterators =
Lists.newArrayListWithCapacity(eventTypes.size());
// 这里返回的subscribers不仅包含event类型的订阅者,也包含event父类的订阅者
for (Class> eventType : eventTypes) {
CopyOnWriteArraySet eventSubscribers = subscribers.get(eventType);
if (eventSubscribers != null) {
// eager no-copy snapshot
subscriberIterators.add(eventSubscribers.iterator());
}
}
return Iterators.concat(subscriberIterators.iterator());
}
static ImmutableSet> flattenHierarchy(Class> concreteClass) {
try {
return flattenHierarchyCache.getUnchecked(concreteClass);
} catch (UncheckedExecutionException e) {
throw Throwables.propagate(e.getCause());
}
}
/** Global cache of classes to their flattened hierarchy of supertypes. */
private static final LoadingCache, ImmutableSet>> flattenHierarchyCache =
CacheBuilder.newBuilder()
.weakKeys()
.build(
new CacheLoader, ImmutableSet>>() {
// > is actually needed to compile
@SuppressWarnings("RedundantTypeArguments")
@Override
public ImmutableSet> load(Class> concreteClass) {
return ImmutableSet.>copyOf(
TypeToken.of(concreteClass).getTypes().rawTypes());
}
});
对于通过eventBus实例post的event,guava提供了几种事件转发策略,EventBus默认是PerThreadQueuedDispatcher,线程间隔离(ThreadLocal实现)
PerThreadQueuedDispatcher
private static final class PerThreadQueuedDispatcher extends Dispatcher {
/** Per-thread queue of events to dispatch. */
private final ThreadLocal> queue =
new ThreadLocal>() {
@Override
protected Queue initialValue() {
return Queues.newArrayDeque();
}
};
/** Per-thread dispatch state, used to avoid reentrant event dispatching. */
private final ThreadLocal dispatching =
new ThreadLocal() {
@Override
protected Boolean initialValue() {
return false;
}
};
@Override
void dispatch(Object event, Iterator subscribers) {
checkNotNull(event);
checkNotNull(subscribers);
Queue queueForThread = queue.get();
// 这里将event和subscribers集合封装成一个事件
queueForThread.offer(new Event(event, subscribers));
// 避免重入,也就是在处理某一个事件的时候,事件内部又通过eventBus实例post事件
// 这个时候就只能放在queue里等待按序处理了
// 重入性相关讨论:https://stackoverflow.com/questions/21947936/guava-eventbus-dispatching
if (!dispatching.get()) {
dispatching.set(true);
try {
Event nextEvent;
while ((nextEvent = queueForThread.poll()) != null) {
while (nextEvent.subscribers.hasNext()) {
// 将事件相关的订阅者都挨个进行"通知"
nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
}
}
} finally {
dispatching.remove();
queue.remove();
}
}
}
private static final class Event {
private final Object event;
private final Iterator subscribers;
private Event(Object event, Iterator subscribers) {
this.event = event;
this.subscribers = subscribers;
}
}
}
// 这里就是事件通知了,executor可以定义成同步的(比如guava的MoreExecutors.directExecutor())
// 要想实现异步,定义成异步的就行
final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
}
异步事件分发器,定义了一个支持多线程安全的全局队列,异步非阻塞需要 executor 来实现
AsyncEventBus就是以这种分发器来做事件分发处理的
private static final class LegacyAsyncDispatcher extends Dispatcher {
/** Global event queue. */
private final ConcurrentLinkedQueue queue =
Queues.newConcurrentLinkedQueue();
@Override
void dispatch(Object event, Iterator subscribers) {
checkNotNull(event);
while (subscribers.hasNext()) {
// 这里是将event和单个subscriber封装成一个事件
queue.add(new EventWithSubscriber(event, subscribers.next()));
}
EventWithSubscriber e;
while ((e = queue.poll()) != null) {
e.subscriber.dispatchEvent(e.event);
}
}
private static final class EventWithSubscriber {
private final Object event;
private final Subscriber subscriber;
private EventWithSubscriber(Object event, Subscriber subscriber) {
this.event = event;
this.subscriber = subscriber;
}
}
}
这个比较简单,就是post一个事件就直接分发出去(深度优先分发),传统观察者模式也就是这种方式
private static final class ImmediateDispatcher extends Dispatcher {
private static final ImmediateDispatcher INSTANCE = new ImmediateDispatcher();
@Override
void dispatch(Object event, Iterator subscribers) {
checkNotNull(event);
while (subscribers.hasNext()) {
subscribers.next().dispatchEvent(event);
}
}
}
EventBus 实现的是同步阻塞的观察者模式。代码上虽然用了线程池 Executor,但实际上,MoreExecutors.directExecutor() 是 Google Guava 提供的工具类,看似是多线程,实际上是单线程。
之所以要这么实现,主要还是为了跟 AsyncEventBus 统一代码逻辑,做到代码复用
private final String identifier;
private final Executor executor;
private final SubscriberExceptionHandler exceptionHandler;
private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
private final Dispatcher dispatcher;
EventBus(
String identifier,
Executor executor,
Dispatcher dispatcher,
SubscriberExceptionHandler exceptionHandler) {
this.identifier = checkNotNull(identifier);
this.executor = checkNotNull(executor);
this.dispatcher = checkNotNull(dispatcher);
this.exceptionHandler = checkNotNull(exceptionHandler);
}
post方法
public void post(Object event) {
Iterator eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
// the event had no subscribers and was not itself a DeadEvent
post(new DeadEvent(this, event));
}
}
register方法
public void register(Object object) {
subscribers.register(object);
}
继承自EventBus,除了Dispatcher使用LegacyAsyncDispatcher外,其他和EventBus一致
为了实现异步非阻塞的观察者模式,它就不能再继续使用 MoreExecutors.directExecutor() 了,而是需要在构造函数中,由调用者注入线程池
public class AsyncEventBus extends EventBus {
public AsyncEventBus(String identifier, Executor executor) {
super(identifier, executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
}
public AsyncEventBus(Executor executor, SubscriberExceptionHandler subscriberExceptionHandler) {
super("default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler);
}
public AsyncEventBus(Executor executor) {
super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
}
}
以上是个人理解,如果问题请指出,谢谢!