Guava学习之EventBus
一、EventBus的使用案例
EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的类和接口层次结构。
Observer模式是比较常用的设计模式之一,虽然有时候在具体代码里,它不一定叫这个名字,比如改头换面叫个Listener,但模式就是这个模式。手工实现一个Observer也不是多复杂的一件事,只是因为这个设计模式实在太常用了,Java就把它放到了JDK里面:Observable和Observer,从JDK 1.0里,它们就一直在那里。从某种程度上说,它简化了Observer模式的开发,至少我们不用再手工维护自己的Observer列表了。不过,如前所述,JDK里的Observer从1.0就在那里了,直到Java 7,它都没有什么改变,就连通知的参数还是Object类型。要知道,Java 5就已经泛型了。Java 5是一次大规模的语法调整,许多程序库从那开始重新设计了API,使其更简洁易用。当然,那些不做应对的程序库,多半也就过时了。这也就是这里要讨论知识更新的原因所在。今天,对于普通的应用,如果要使用Observer模式该如何做呢?答案是Guava的EventBus。
EventBus基本用法:
使用Guava之后, 如果要订阅消息, 就不用再继承指定的接口, 只需要在指定的方法上加上@Subscribe注解即可。代码如下:
消息封装类:

public class TestEvent {
private final int message;
public TestEvent(int message) {
this.message = message;
System.out.println("event message:"+message);
}
public int getMessage() {
return message;
}
}

消息接受类:

public class EventListener {
public int lastMessage = 0; @Subscribe
public void listen(TestEvent event) {
lastMessage = event.getMessage();
System.out.println("Message:"+lastMessage);
} public int getLastMessage() {
return lastMessage;
}
}

测试类及输出结果:

public class TestEventBus {
@Test
public void testReceiveEvent() throws Exception { EventBus eventBus = new EventBus("test");
EventListener listener = new EventListener(); eventBus.register(listener); eventBus.post(new TestEvent(200));
eventBus.post(new TestEvent(300));
eventBus.post(new TestEvent(400)); System.out.println("LastMessage:"+listener.getLastMessage());
;
}
} //输出信息
event message:200
Message:200
event message:300
Message:300
event message:400
Message:400
LastMessage:400

MultiListener的使用:
只需要在要订阅消息的方法上加上@Subscribe注解即可实现对多个消息的订阅,代码如下:

public class MultipleListener {
public Integer lastInteger;
public Long lastLong; @Subscribe
public void listenInteger(Integer event) {
lastInteger = event;
System.out.println("event Integer:"+lastInteger);
} @Subscribe
public void listenLong(Long event) {
lastLong = event;
System.out.println("event Long:"+lastLong);
} public Integer getLastInteger() {
return lastInteger;
} public Long getLastLong() {
return lastLong;
}
}

测试类:

public class TestMultipleEvents {
@Test
public void testMultipleEvents() throws Exception { EventBus eventBus = new EventBus("test");
MultipleListener multiListener = new MultipleListener(); eventBus.register(multiListener); eventBus.post(new Integer(100));
eventBus.post(new Integer(200));
eventBus.post(new Integer(300));
eventBus.post(new Long(800));
eventBus.post(new Long(800990));
eventBus.post(new Long(800882934)); System.out.println("LastInteger:"+multiListener.getLastInteger());
System.out.println("LastLong:"+multiListener.getLastLong());
}
} //输出信息
event Integer:100
event Integer:200
event Integer:300
event Long:800
event Long:800990
event Long:800882934
LastInteger:300
LastLong:800882934

Dead Event:
如果EventBus发送的消息都不是订阅者关心的称之为Dead Event。实例如下:

public class DeadEventListener {
boolean notDelivered = false; @Subscribe
public void listen(DeadEvent event) { notDelivered = true;
} public boolean isNotDelivered() {
return notDelivered;
}
}

测试类:

public class TestDeadEventListeners {
@Test
public void testDeadEventListeners() throws Exception { EventBus eventBus = new EventBus("test");
DeadEventListener deadEventListener = new DeadEventListener();
eventBus.register(deadEventListener); eventBus.post(new TestEvent(200));
eventBus.post(new TestEvent(300)); System.out.println("deadEvent:"+deadEventListener.isNotDelivered()); }
} //输出信息
event message:200
event message:300
deadEvent:true

说明:如果没有消息订阅者监听消息, EventBus将发送DeadEvent消息,这时我们可以通过log的方式来记录这种状态。
Event的继承:
如果Listener A监听Event A, 而Event A有一个子类Event B, 此时Listener A将同时接收Event A和B消息,实例如下:
Listener 类:

public class NumberListener { private Number lastMessage; @Subscribe
public void listen(Number integer) {
lastMessage = integer;
System.out.println("Message:"+lastMessage);
} public Number getLastMessage() {
return lastMessage;
}
} public class IntegerListener { private Integer lastMessage; @Subscribe
public void listen(Integer integer) {
lastMessage = integer;
System.out.println("Message:"+lastMessage);
} public Integer getLastMessage() {
return lastMessage;
}
}

测试类:

public class TestEventsFromSubclass {
@Test
public void testEventsFromSubclass() throws Exception { EventBus eventBus = new EventBus("test");
IntegerListener integerListener = new IntegerListener();
NumberListener numberListener = new NumberListener();
eventBus.register(integerListener);
eventBus.register(numberListener); eventBus.post(new Integer(100)); System.out.println("integerListener message:"+integerListener.getLastMessage());
System.out.println("numberListener message:"+numberListener.getLastMessage()); eventBus.post(new Long(200L)); System.out.println("integerListener message:"+integerListener.getLastMessage());
System.out.println("numberListener message:"+numberListener.getLastMessage());
}
} //输出类
Message:100
Message:100
integerListener message:100
numberListener message:100
Message:200
integerListener message:100
numberListener message:200

说明:在这个方法中,我们看到第一个事件(新的整数(100))是收到两个听众,但第二个(新长(200 l))只能到达NumberListener作为整数一不是创建这种类型的事件。可以使用此功能来创建更通用的监听器监听一个广泛的事件和更详细的具体的特殊的事件。
一个综合实例:

public class UserThread extends Thread {
private Socket connection;
private EventBus channel;
private BufferedReader in;
private PrintWriter out; public UserThread(Socket connection, EventBus channel) {
this.connection = connection;
this.channel = channel;
try {
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
out = new PrintWriter(connection.getOutputStream(), true);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
} @Subscribe
public void recieveMessage(String message) {
if (out != null) {
out.println(message);
System.out.println("recieveMessage:"+message);
}
} @Override
public void run() {
try {
String input;
while ((input = in.readLine()) != null) {
channel.post(input);
}
} catch (IOException e) {
e.printStackTrace();
} //reached eof
channel.unregister(this);
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
out = null;
}
}


mport java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; import com.google.common.eventbus.EventBus; public class EventBusChat {
public static void main(String[] args) {
EventBus channel = new EventBus();
ServerSocket socket;
try {
socket = new ServerSocket(4444);
while (true) {
Socket connection = socket.accept();
UserThread newUser = new UserThread(connection, channel);
channel.register(newUser);
newUser.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

说明:用telnet命令登录:telnet 127.0.0.1 4444 ,如果你连接多个实例你会看到任何消息发送被传送到其他实例。
二、EventBus源码解读
Register函数
public void register(Object object) {
Multimap<Class<?>, EventSubscriber> methodsInListener =
finder.findAllSubscribers(object);
subscribersByTypeLock.writeLock().lock();
try {
subscribersByType.putAll(methodsInListener);
} finally {
subscribersByTypeLock.writeLock().unlock();
}
}
methodsInListener中会根据@Subscribe的注解查找所传入对象的EventHandler,并将事件类型与EventSubscriber的映射保留在subscribersByType对象中。
再来看AnnotatedSubscriberFinder中的findAllSubscribers方法:
@Override
public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) {
Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
EventSubscriber subscriber = makeSubscriber(listener, method);
methodsInListener.put(eventType, subscriber);
}
return methodsInListener;
}
上面代码的内容就是通过反射读取Subscriber对象中带有@Subscribe注解的方法,并使用makeSubscriber将Subscriber对象和它的带@Subscribe对象的method对象封装成EventSubscriber标准事件处理对象:
private static EventSubscriber makeSubscriber(Object listener, Method method) {
EventSubscriber wrapper;
if (methodIsDeclaredThreadSafe(method)) {
wrapper = new EventSubscriber(listener, method);
} else {
wrapper = new SynchronizedEventSubscriber(listener, method);
}
return wrapper;
}
Post函数:
public void post(Object event) {
Set<Class<?>> dispatchTypes = this.flattenHierarchy(event.getClass());
boolean dispatched = false;
Iterator i$ = dispatchTypes.iterator(); while(i$.hasNext()) {
Class<?> eventType = (Class)i$.next();
this.subscribersByTypeLock.readLock().lock(); try {
Set<EventSubscriber> wrappers = this.subscribersByType.get(eventType);
if (!wrappers.isEmpty()) {
dispatched = true;
Iterator i$ = wrappers.iterator(); while(i$.hasNext()) {
EventSubscriber wrapper = (EventSubscriber)i$.next();
this.enqueueEvent(event, wrapper);
}
}
} finally {
this.subscribersByTypeLock.readLock().unlock();
}
} if (!dispatched && !(event instanceof DeadEvent)) {
this.post(new DeadEvent(this, event));
} this.dispatchQueuedEvents();
}
void enqueueEvent(Object event, EventSubscriber subscriber) {
((Queue)this.eventsToDispatch.get()).offer(new EventBus.EventWithSubscriber(event, subscriber));
}
void dispatchQueuedEvents() {
if (!(Boolean)this.isDispatching.get()) {
this.isDispatching.set(true); try {
Queue events = (Queue)this.eventsToDispatch.get(); EventBus.EventWithSubscriber eventWithSubscriber;
while((eventWithSubscriber = (EventBus.EventWithSubscriber)events.poll()) != null) {
this.dispatch(eventWithSubscriber.event, eventWithSubscriber.subscriber);
}
} finally {
this.isDispatching.remove();
this.eventsToDispatch.remove();
} }
}
void dispatch(Object event, EventSubscriber wrapper) {
try {
wrapper.handleEvent(event);
} catch (InvocationTargetException var6) {
InvocationTargetException e = var6; try {
this.subscriberExceptionHandler.handleException(e.getCause(), new SubscriberExceptionContext(this, event, wrapper.getSubscriber(), wrapper.getMethod()));
} catch (Throwable var5) {
Logger.getLogger(EventBus.class.getName()).log(Level.SEVERE, String.format("Exception %s thrown while handling exception: %s", var5, var6.getCause()), var5);
}
} }
public void handleEvent(Object event) throws InvocationTargetException {
Preconditions.checkNotNull(event); String var3;
try {
this.method.invoke(this.target, event);
} catch (IllegalArgumentException var4) {
var3 = String.valueOf(String.valueOf(event));
throw new Error((new StringBuilder(33 + var3.length())).append("Method rejected target/argument: ").append(var3).toString(), var4);
} catch (IllegalAccessException var5) {
var3 = String.valueOf(String.valueOf(event));
throw new Error((new StringBuilder(28 + var3.length())).append("Method became inaccessible: ").append(var3).toString(), var5);
} catch (InvocationTargetException var6) {
if (var6.getCause() instanceof Error) {
throw (Error)var6.getCause();
} else {
throw var6;
}
}
}
总结一下
1.首先是Register方法把加了@Subscribe注解方法的listener和方法通过反射方式注册到缓存的map中 格式为,参数类型:EventSubscriber
EventSubscriber中包含listerner类和方法名。
2.post时首先根据post中的参数类型找到对应的EventSubscriber,然后把EventSubscriber放入当前线程的对列Queue中,然后去执行当前线程的这个队列
三、手动实现EventBus和AsyncEventBus案例
首先导入guava的依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
目录结构介绍:
event:一个自定义的事件类,类的内容随意定义。
eventListeners:定义了两个事件监听者类,类里面的方法加@Subscribe注解。
util:eventBus工具类。
TestMain类,测试函数。
本案例在EventBusUtil工具类中声明了EventBus和AsyncEventBus两个变量,因此,在后面演示AsyncEventBus的使用时,只需要更改TestMain中的post()方法即可。
下面逐一上代码。
CustomEvent类代码
public class CustomEvent {
private int age;
public CustomEvent(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
}
EventListener1.java类代码
public class EventListener1 {
@Subscribe
public void test1(CustomEvent event){
System.out.println(Instant.now() +"监听者1-->订阅者1,收到事件:"+event.getAge()+",线程号为:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Subscribe
public void test2(CustomEvent event){
System.out.println(Instant.now() +"监听者1-->订阅者2,收到事件:"+event.getAge()+",线程号为:"+Thread.currentThread().getName());
}
}
EventListener2.java类代码
public class EventListener2 {
@Subscribe
public void test(CustomEvent event){
System.out.println(Instant.now() +",监听者2,收到事件:"+event.getAge()+",线程号为:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
可以看到,两个监听者类,一共定义了三个订阅者,三个订阅者订阅的都是同一个事件对象。待会观察一下EventBus同步的方式下,收到事件之后订阅者们的处理方式。
工具类代码:
public class EventBusUtil {
private static EventBus eventBus;
private static AsyncEventBus asyncEventBus;
private static Executor executor = new Executor() {
public void execute(Runnable command) {
new Thread(command).start();
}
};
//双重锁单例模式
private static AsyncEventBus getAsynEventBus(){
if(asyncEventBus==null){
synchronized (AsyncEventBus.class){
if(asyncEventBus==null){
asyncEventBus = new AsyncEventBus(executor);
}
}
}
return asyncEventBus;
}
//双重锁单例模式
private static EventBus getEventBus(){
if(eventBus==null){
synchronized (EventBus.class){
if(eventBus==null){
eventBus = new EventBus();
}
}
}
return eventBus;
}
public static void post(Object event){
getEventBus().post(event);
}
//异步方式发送事件
public static void asyncPost(Object event){
getAsynEventBus().post(event);
}
public static void register(Object object){
getEventBus().register(object);
getAsynEventBus().register(object);
} }
测试类代码:
public class TestMain {
public static void main(String[] args) {
EventListener1 listener1 = new EventListener1();
EventListener2 listener2 = new EventListener2();
CustomEvent customEvent = new CustomEvent(23);
EventBusUtil.register(listener1);
EventBusUtil.register(listener2);
EventBusUtil.post(customEvent);
// EventBusUtil.asyncPost(customEvent);
// try {
// Thread.sleep(10*1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Instant.now() +",主线程执行完毕:"+Thread.currentThread().getName());
}
}
上面是测试类,创建了事件监听者的对象,并且注册给了EventBus,调用EventBus的同步post方法执行。结果如下:
2019-09-04T09:05:35.420Z,监听者1-->订阅者1,收到事件:23,线程号为:main
2019-09-04T09:05:38.480Z,监听者1-->订阅者2,收到事件:23,线程号为:main
2019-09-04T09:05:38.480Z,监听者2,收到事件:23,线程号为:main
2019-09-04T09:05:41.485Z,主线程执行完毕:main
同步EventBus总结规律:可以看到每一个事件的消费方在执行时,都是用的调用方的线程,并且同一时间只能同时执行一个订阅者的方法。从Listener1里的方法比Listener2里的方法先执行可以看出,先注册到EventBus的订阅者在收到事件后会先执行。
那么测试一下异步发送事件的结果,将上面注释打开,切换到EventBusUtil.asyncPost()方法,日志如下:
2019-09-04T09:12:14.010Z,监听者1-->订阅者2,收到事件:23,线程号为:Thread-2
2019-09-04T09:12:14.010Z,监听者2,收到事件:23,线程号为:Thread-3
2019-09-04T09:12:14.010Z,监听者1-->订阅者1,收到事件:23,线程号为:Thread-1
2019-09-04T09:12:24.014Z,主线程执行完毕:main
异步执行,三个订阅者同时执行,并且是为事件消费方重新开的一个新的线程去执行自己的任务,互相不等待。
这里由于并行执行,订阅者的方法中有sleep,因此也让主线程进行了10秒的等待。
四、EventBus和AsyncEventBus使用区别
上面的测试案例简单,并且很能说明问题。
EventBus:同步事件总线
1.同步执行,事件发送方在发出事件之后,会等待所有的事件消费方执行完毕后,才会回来继续执行自己后面的代码。
2.事件发送方和事件消费方会在同一个线程中执行,消费方的执行线程取决于发送方。
3.同一个事件的多个订阅者,在接收到事件的顺序上面有不同。谁先注册到EventBus的,谁先执行,如果是在同一个类中的两个订阅者一起被注册到EventBus的情况,收到事件的顺序跟方法名有关。
AsyncEventBus:异步事件总线
1.异步执行,事件发送方异步发出事件,不会等待事件消费方是否收到,直接执行自己后面的代码。
2.在定义AsyncEventBus时,构造函数中会传入一个线程池。事件消费方收到异步事件时,消费方会从线程池中获取一个新的线程来执行自己的任务。
3.同一个事件的多个订阅者,它们的注册顺序跟接收到事件的顺序上没有任何联系,都会同时收到事件,并且都是在新的线程中,异步并发的执行自己的任务。
参考:https://blog.csdn.net/xfdingustc/article/details/43528821
https://www.cnblogs.com/peida/p/EventBus.html
https://blog.csdn.net/qq_38345296/article/details/100539989
Guava学习之EventBus的更多相关文章
- Guava学习笔记目录
Guava 是一个 Google 的基于java1.6的类库集合的扩展项目,包括 collections, caching, primitives support, concurrency libra ...
- Guava学习
Guava学习笔记目录 Guava 是一个 Google 的基于java1.6的类库集合的扩展项目,包括 collections, caching, primitives support, concu ...
- guava 学习笔记 使用瓜娃(guava)的选择和预判断使代码变得简洁
guava 学习笔记 使用瓜娃(guava)的选择和预判断使代码变得简洁 1,本文翻译自 http://eclipsesource.com/blogs/2012/06/06/cleaner-code- ...
- guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用
guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用 1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection ...
- [置顶] Guava学习之ArrayListMultimap
ArrayListMultimap类的继承关系如下图所示: Guava ArrayListMultimap List Multimap 是一个接口,继承自 Multimap 接口.ListMultim ...
- [置顶] Guava学习之Splitter
Splitter:在Guava官方的解释为:Extracts non-overlapping substrings from an input string, typically by recogni ...
- [置顶] Guava学习之Iterators
Iterators类提供了返回Iterator类型的对象或者对Iterator类型对象操作的方法.除了特别的说明,Iterators类中所有的方法都在Iterables类中有相应的基于Iterable ...
- [置顶] Guava学习之Lists
Lists类主要提供了对List类的子类构造以及操作的静态方法.在Lists类中支持构造ArrayList.LinkedList以及newCopyOnWriteArrayList对象的方法.其中提供了 ...
- [置顶] Guava学习之Immutable集合
Immutable中文意思就是不可变.那为什么需要构建一个不可变的对象?原因有以下几点: 在并发程序中,使用Immutable既保证线程安全性,也大大增强了并发时的效率(跟并发锁方式相比).尤其当一个 ...
随机推荐
- Java二、八、十、十六进制介绍
1.说明 在Java中整数有四种表示方式, 分别为十进制,二进制,八进制,十六进制, 其中十进制就是平常最熟悉,使用最多的进制: 二进制是在计算机中使用最多的进制, 八进制和十六进制都是基于二进制的, ...
- SpringCloud创建Config Client配置读取
1.说明 本文详细介绍配置中心客户端使用方法, 即Config Client到Config Server读取配置, 这里以创建Config Client服务为例, 基于已经创建好的Config Ser ...
- 包含全国所有省份、城市、县的一份json文件
最近做项目时,有个需要全国所有省市信息的数据,于是百度了一下,发现CSDN的很多都需要积分下载,无解!所以自己收集了一份整理了出来. 简单说明一下 1.这是一份json文件,这是因为全国的省市信息一般 ...
- 下面哪些命令可以查看file1文件的第300-500行的内容?
下面哪些命令可以查看file1文件的第300-500行的内容? cat file1 | tail -n +300 | head -n 200 cat file1| head -n 500 | tail ...
- vue部署服务器以及解决部署到apache路由出现404
最近在开发cms的时候使用Vue.js框架,利用vue-route.vue-cli结合webpack编写了一个单页路由项目,自己在服务器端配置apache.部署完成后,访问没问题,从页面中点击跳转就会 ...
- vue2.0中实现echarts图片下载-----书写中
由于各个版本浏览器兼容性不一,所以,我们需要一个判断浏览器类型的函数来对不同的浏览器做不同的处理. 获取浏览器版本的函数 // 判断浏览器类型 IEVersion () { let userAgent ...
- Underscore.js 1.3.3 源码分析收藏
Underscore是一个提供许多函数编程功能的库,里面包含了你期待(在Prototype.js和Ruby中)的许多功能.但是没有扩展任何内置的Javascript对象,也就是说它没有扩展任何内置对象 ...
- 关于CKCsec安全研究院
关于CKCsec安全研究院 CKCsec安全研究院所有文档开源于语雀,会源源不断更新. 部分内容 微信公众号 知识星球 使用需知 由于传播.利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均 ...
- 《剑指offer》面试题18. 删除链表的节点
问题描述 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点. 返回删除后的链表的头节点. 注意:此题对比原题有改动 示例 1: 输入: head = [4,5,1,9], val = ...
- 《剑指offer》面试题32 - III. 从上到下打印二叉树 III
问题描述 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推. 例如: 给定二叉树: [3,9,20, ...