观察者模式及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。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。

下图描述的观察者模式中有两个主要角色:Subject(主题)和 Observer(观察者)。

观察者模式是一个比较抽象的模式,根据不同的应用场景和需求,有完全不同的实现方式,先来看其中最经典的一种实现方式。这也是在讲到这种模式的时候,很多书籍或资料给出的最常见的实现方式。具体的代码如下所示,先定义两个观察者与被观察者接口,然后实现之。

//被观察者接口
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还通过加ReentrantLock锁的方式,避免了并发写冲突

观察者注册表中的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);
    }
}

测试结果如下:

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值