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既保证线程安全性,也大大增强了并发时的效率(跟并发锁方式相比).尤其当一个 ...
随机推荐
- Dynamic Routing Between Capsules
目录 概 主要内容 损失函数 代码 Sabour S, Frosst N, Hinton G E, et al. Dynamic Routing Between Capsules[C]. neural ...
- 单芯片替代PS176 DP转HDMI 4K60HZ DP转HDMI2.0转换芯片CS5263
PS176是一个显示端口 (DP)至HDMI 2.0视频接口转换器适用于需要视频协议转换的电缆适配器.电视接收器.监视器和其他应用.它将接受任何显示端口输入格式,包括DP 1.1a.dp1.2a.dp ...
- BUUCTF [极客大挑战 2019]Not Bad
总的来说这是一个64位orw的题 开头先在主函数里分配了一个很大的空间 1 __int64 __fastcall main(int a1, char **a2, char **a3) 2 { 3 mm ...
- Eclipse导入Zookeeper源码Version2017.11.3
将Zookeeper源码导入Eclipse, Zookeeper源码需要使用ant构建后才能导入Eclipse, 和Solr的源码一样也是使用ant构建的, 大部分可以参考Eclipse导入Solr源 ...
- Flask_上下文(六)
Flask中有两种上下文,应用上下文(application context)和请求上下文(request context) 当客户端发来请求时,请求上下文就登场了.请求上下文里包含了请求的各种信息, ...
- go.mod file not found in current directory or any parent directory; see 'go help modules'
go的环境设置问题,执行 go env -w GO111MODULE=auto 我的环境:Windows 7, Go 1.17 D:\Apps\GOPATH\src\code.oldboyedu.co ...
- 在CentOS7 安装 jq
root@: 安装EPEL源: yum install epel-release 安装完EPEL源后,可以查看下jq包是否存在: yum list jq 安装jq: yum install jq 命令 ...
- 《手把手教你》系列技巧篇(五十六)-java+ selenium自动化测试-下载文件-上篇(详细教程)
1.简介 前边几篇文章讲解完如何上传文件,既然有上传,那么就可能会有下载文件.因此宏哥就接着讲解和分享一下:自动化测试下载文件.可能有的小伙伴或者童鞋们会觉得这不是很简单吗,还用你介绍和讲解啊,不说就 ...
- 以太 ip tcp udp 三次握手的理解
以太帧: 1.前导码(7字节):使接收器建立比特同步. 2.起始定界符SFD(1字节):指示一帧的开始. 3.目的地址DA(6字节):指出要接收该帧的工作站. 4.源地址SA(6字节):指示发送该帧的 ...
- 问题记录——BigDecimal保留两位小数及格式化成百分比
1.函数总结 BigDecimal.setScale()方法用于格式化小数点 setScale(1)表示保留一位小数,默认用四舍五入方式 setScale(1,BigDecimal.ROUND_DOW ...