观察者模式(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翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。其中,Google Guava EventBus就是一个比较著名的 EventBus框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。guava包中的EventBus是一个事件通知组件,可以用来在同一个JVM中,实现事件通知机制,这里和Android的EventBus不是一个事物。在一些业务场景中,我们会使用Redis队列或者Kafka等其他MQ来实现分布式多进程间的消息通知,而EventBus是在单个JVM中使用。可以对代码逻辑进行相应的解耦,某些场景下,比如异步调用,可以提高整体的运行性能。
EventBus实际上是一个消息队列,Event Source发送一个消息到EventBus,然后再由EventBus将消息推送到所监听的Listener。
EventBus包含两个大的模块:发布器与订阅器。
发布器主要是两个方法:
订阅器就比较简单,只有一个@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类核心代码如下:
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();
}
//其他代码省略
}
读完以上源码,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);
}
}