EventBus VS Spring Event

本地异步处理,采用事件机制 可以使 代码解耦,更易读。事件机制实现模式是 观察者模式(或发布订阅模式),主要分为三部分:发布者、监听者、事件。

Guava EventBus

Guava EventBus实现是观察者模式,用法很简单,先上代码。

不止是代码
/**
* Desc: 事件对象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HelloEvent {
private String eventName;
} @Data
@NoArgsConstructor
public class WorldEvent extends HelloEvent { private int eventNo; public WorldEvent(String name, int no) {
setEventName(name);
setEventNo(no);
}
} /**
* Desc: 事件监听器,可以监听多个事件。处理方法添加 @Subscribe 注解即可。
*/
public class GeventListener { /**
* 监听 HelloEvent 类型及其父类型(Object)的事件
*/
@Subscribe
public void processEvent(HelloEvent event){
System.out.println("process hello event, name:" + event.getEventName());
} /**
* 监听 WorldEvent 类型及其父类型(HelloEvent 和 Object)的事件
*/
@Subscribe
public void processWorldEvent(WorldEvent event) {
System.out.println("process world eventV1, no:" + event.getEventNo() + ", name:" + event.getEventName());
} /**
* 注册多个监听器 监听同一事件
* @param event
*/
@Subscribe
public void processWorldEventV2(WorldEvent event) {
System.out.println("process world eventV2, no:" + event.getEventNo() + ", name:" + event.getEventName());
} @Subscribe
public void processObject(Object object) {
System.out.println("process common event, class:" + object.getClass().getSimpleName());
}
} public class GuavaTest { public static void main(String[] args) {
EventBus eventBus = new EventBus();
GeventListener listener = new GeventListener();
eventBus.register(listener); eventBus.post(new HelloEvent("hello"));
eventBus.post(new WorldEvent("world", 23333));
}
}

结果如下:

//HelloEvent被两个监听器处理(HelloEvent类及Object类的监听器)
process hello event, name:hello
process common event, class:HelloEvent
//WorldEvent被四个监听器处理(两个自己的,两个父类的)
process world eventV1, no:23333, name:world
process world eventV2, no:23333, name:world
process hello event, name:world
process common event, class:WorldEvent

由上可知:Guava EventBus把类当做事件,是以class为key注册和管理事件的,value是事件监听器的method;事件监听器只处理某一类(及其父类)事件。

事件注册与发布
//com.google.common.eventbus.EventBus#register
public void register(Object object) {
//key为Class, value为EventSubscriber(Object target, Method method)【集合】。注意这里Multimap 为HashMultimap, 即HashMap<K, Collection<V>>
Multimap<Class<?>, EventSubscriber> methodsInListener =
finder.findAllSubscribers(object);
subscribersByTypeLock.writeLock().lock();
try {
subscribersByType.putAll(methodsInListener);
} finally {
subscribersByTypeLock.writeLock().unlock();
}
} //com.google.common.eventbus.EventBus#post
public void post(Object event) {
//找到event类及其所有父类
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass()); boolean dispatched = false;
for (Class<?> eventType : dispatchTypes) {
subscribersByTypeLock.readLock().lock();
try {
//找到所有事件订阅者(事件监听器)
Set<EventSubscriber> wrappers = subscribersByType.get(eventType); if (!wrappers.isEmpty()) {
dispatched = true;
for (EventSubscriber wrapper : wrappers) {
//事件入队列
enqueueEvent(event, wrapper);
}
}
} finally {
subscribersByTypeLock.readLock().unlock();
}
} //如果没有订阅者订阅此类消息,则为 DeadEvent
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
} dispatchQueuedEvents();
}
事件隔离

多个EventBus可以隔离事件。

public class AnotherListener {
/**
* 监听 WorldEvent 类型及其父类型(HelloEvent 和 Object)的事件
*/
@Subscribe
public void processAnotherWorldEvent(WorldEvent event) {
System.out.println("process another world event, no:" + event.getEventNo() + ", name:" + event.getEventName());
}
} public class GuavaTest { public static void main(String[] args) {
EventBus eventBus = new EventBus();
GeventListener listener = new GeventListener();
eventBus.register(listener);
eventBus.post(new HelloEvent("hello")); EventBus anotherEventBus = new EventBus();
AnotherListener anotherListener = new AnotherListener();
anotherEventBus.register(anotherListener);
anotherEventBus.post(new WorldEvent("AnotherWorld", 666));
}
}

结果是

//eventBus结果与之前相同
process hello event, name:hello
//anotherEventBus 发布的事件,只被其注册的监听器处理
process common event, class:HelloEvent
process another world event, no:666, name:AnotherWorld

适用场景:

  • 按照类区分事件
  • 订阅 事件簇
  • 支持自定义event,可以根据event自己写分发器
  • 事件隔离

spring event

spring 新版事件机制也比较简单,看代码。

不止是代码
/**
* 继承 ApplicationEvent 的事件
*/
@Data
public class HelloEvent extends ApplicationEvent { private String eventName; public HelloEvent(String eventName) {
super(eventName);
setEventName(eventName);
}
} /**
* 自定义事件
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomerEvent {
private String name;
private Boolean isCustomer;
} /**
* 监听器类,spring也支持一个类中监听多个事件
*/
@Component("springListener")
public class SpringListener { /**
* 监听所有ApplicationEvent类型 及其子类型 的事件
*/
@EventListener
public void processApplicationEvent(ApplicationEvent event) {
System.out.println("process common event, class:" + event.getClass().getSimpleName());
} /**
* 监听 HelloEvent类型 事件
*/
@EventListener
public void processHelloEvent(HelloEvent event) {
System.out.println("process helloEvent, name:" + event.getEventName());
} /**
* 监听 CustomerEvent 类型事件,但是需要满足condition条件,即isCustomer=true
*/
@EventListener(condition = "#event.isCustomer")
public void processCustomerEvent(CustomerEvent event) {
System.out.println("process customer CustomerEvent, name:" + event.getName());
} /**
* 监听 CustomerEvent 类型事件,但是需要满足condition条件,即name="miaomiao"
*/
@EventListener(condition = "#event.getName().equals('miaomiao')")
public void processMiaoMiaoEvent(CustomerEvent event) {
System.out.println("process miaomiao's CustomerEvent, name:" + event.getName());
} /**
* 支持异步处理事件
*/
@Async
@EventListener
public void processAsyncCustomerEvent(CustomerEvent event) {
System.out.println("Async process CustomerEvent, name:" + event.getName());
}
} //执行类,测试入口
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.manyao.async"})
public class DemoApplication {
public static void main(String[] args) throws TException {
SpringApplication.run(DemoApplication.class, args); ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
String[] names = context.getBeanDefinitionNames();
for(int i=0; i<names.length; i++) {
System.out.println(names[i]);
}
System.out.println("++++++++++");
context.publishEvent(new HelloEvent("helloEvent"));
context.publishEvent(new CustomerEvent("customer", true));
context.publishEvent(new CustomerEvent("miaomiao", false));
}
}

结果是

//以下是spring上下文event,继承自 ApplicationContextEvent。 用于用户参与上下文生命周期的入口。因为是ApplicationEvent子类型,所以,由processApplicationEvent处理。
process common event, class:ContextRefreshedEvent
process common event, class:EmbeddedServletContainerInitializedEvent
process common event, class:ApplicationReadyEvent
process common event, class:ContextRefreshedEvent
//以下是上下文中的bean
springListener
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
++++++++++
//HelloEvent 继承 ApplicationEvent,会被processApplicationEvent处理
process common event, class:HelloEvent
//监听 HelloEvent类型 的 processHelloEvent 处理
process helloEvent, name:helloEvent
//非 ApplicationEvent 的事件,则为 PayloadApplicationEvent
process common event, class:PayloadApplicationEvent
//isCustomer=true,符合processCustomerEvent处理条件
process customer CustomerEvent, name:customer
//监听CustomerEvent类型,处理结果
Async process CustomerEvent, name:customer
process common event, class:PayloadApplicationEvent
//符合processMiaoMiaoEvent条件
process miaomiao's CustomerEvent, name:miaomiao
Async process CustomerEvent, name:miaomiao
//spring 上下文事件
process common event, class:ContextClosedEvent
spring 上下文事件

上述例子中的

ContextRefreshedEvent,EmbeddedServletContainerInitializedEvent,ApplicationReadyEvent,ContextRefreshedEvent,ContextClosedEvent 等事件,都是spring上下文事件。可以通过监听这些事件,参与到spring生命周期中去。这种无侵入性交互方式,在做平台服务时,是一种很好的方式。

注册监听器

org.springframework.context.event.EventListenerMethodProcessor#processBean 将所有注解EventListener的方法,存入上下文的applicationListeners中。Listener的封装类为ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method)。

org.springframework.context.support.AbstractApplicationContext#refresh 中调用 initApplicationEventMulticaster 初始化事件发布管理器applicationEventMulticaster,然后调用registerListeners() 注册监听器。

发布事件

spring 起初只支持 ApplicationEvent类型事件,后来优化之后,支持自定义事件。自定义事件的处理,默认为PayloadApplicationEvent,相当于EventBus的DeadEvent。

//org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
protected void publishEvent(Object event, ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
} // Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
//若不是ApplicationEvent类型,则使用PayloadApplicationEvent封装
applicationEvent = new PayloadApplicationEvent<Object>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
} // Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//核心操作,初始化 event
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
} //调用父类,发布事件
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
执行事件
	@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//获取事件的监听器集合,并逐个触发执行监听器
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//异步的话,就放在线程池中执行
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
//本线程调用
invokeListener(listener, event);
}
}
}

可以看到,spring的事件机制更复杂,但是功能同样强大。

适用场景:

  • 按照类区分事件
  • 订阅 事件簇
  • 支持自定义event
  • 按照condition过滤同类型事件

比较EventBus与Spring Event

  • 使用方式比较
项目 事件 发布者 发布方法 是否异步 监听者 注册方式
EventBus 任意对象 EventBus EventBus#post 注解Subscribe方法 手动注册EventBus#register
Spring Event 任意对象 ApplicationEventPublisher ApplicationEventPublisher#publishEvent 支持同步异步 注解EventListener方法 系统注册
  • 使用场景比较
项目 事件区分 是否支持事件簇 是否支持自定义event 是否支持过滤 是否支持事件隔离 复杂程度
EventBus Class 简单
Spring Event Class 复杂

EventBus VS Spring Event的更多相关文章

  1. Spring Event事件驱动

    Spring事件驱动模型,简单来说类似于Message-Queue消息队列中的Pub/Sub发布/订阅模式,也类似于Java设计模式中的观察者模式. 自定义事件 Spring的事件接口位于org.sp ...

  2. spring event

    昨天看到了一遍关于spring event的帖子,觉得很好,就照着敲了一份代码,感觉对spring event有了进一步的认识.帖子链接:https://segmentfault.com/a/1190 ...

  3. 自定义Spring event

    通过Spring自定义event 首先我们定义我们的event类 package com.hyenas.spring.custom.event; import org.springframework. ...

  4. Guava EventBus集成spring

    EventBus 不是通用的消息系统,也不是用来做进程间的通信的,而是在进程内,用于解耦两段直接调用的业务逻辑: 1.代码结构 event:eventbus中流转的事件(消息),包结构按照业务模块在细 ...

  5. Event Handling in Spring

    Spring内置的event有 1.ContextRefreshedEvent This event is published when the ApplicationContext is eithe ...

  6. spring中自定义Event事件的使用和浅析

    在我目前接触的项目中,用到了许多spring相关的技术,框架层面的spring.spring mvc就不说了,细节上的功能也用了不少,如schedule定时任务.Filter过滤器. intercep ...

  7. Spring学习六:自定义Event事件

    Spring 中的自定义事件 编写和发布自己的自定义事件有许多步骤.按照在这一章给出的说明来编写,发布和处理自定义 Spring 事件. 步骤 描述 1 创建一个名称为 SpringExample 的 ...

  8. Guava学习笔记:EventBus(转)

    EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现.对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的类和 ...

  9. 快速Android开发系列通信篇之EventBus

    先吐槽一下博客园的MarkDown编辑器,推出的时候还很高兴博客园支持MarkDown了,试用了下发现支持不完善就没用了,这次这篇是在其他编辑器下写的,复制过来后发现..太烂了.怎么着作为一个技术博客 ...

随机推荐

  1. java打包项目将配置文件放在包外面(后续还会有补充)

    项目中也经常单独将一部分功能独立做Java Project,然后打成jar包供其他项目调用.如果jar包中需要读取配置文件信息,则很少把该配置打进jar包,因为它不方便修改,更多都是采用jar包读取外 ...

  2. java url demo

    // File Name : URLDemo.java import java.net.*; import java.io.*; public class URLDemo { public stati ...

  3. Tesseract OCR win 32位编译

    https://github.com/tesseract-ocr/tesseract/wiki/Compiling 找到该标题:Develop Tesseract 按照上面的步骤执行即可,最后使用 v ...

  4. Service IP 原理 - 每天5分钟玩转 Docker 容器技术(137)

    Service Cluster IP 是一个虚拟 IP,是由 Kubernetes 节点上的 iptables 规则管理的. 可以通过 iptables-save 命令打印出当前节点的 iptable ...

  5. 项目实战14—ELK 企业内部日志分析系统

    一.els.elk 的介绍 1.els,elk els:ElasticSearch,Logstash,Kibana,Beats elk:ElasticSearch,Logstash,Kibana ① ...

  6. URL末尾处的斜杠“/”

    在输入网址的时候,比如输入"http://www.xxx.com/",此URL中末尾的斜杠是至关重要的.因为在这种情况下,浏览器能安全的添加斜杠.而像"http://ww ...

  7. docker学习笔记(二)

    一.常用Linux 命令 ls -a(同时列出隐含文件), -l(输出一个比较完整的格式,除每个文件名外,增加显示文件 类型.权限.硬链接数.所有者名.组名.大小(byte).及时间信息-----简化 ...

  8. 深度学习菜鸟的信仰地︱Supervessel超能云服务器、深度学习环境全配置

    并非广告~实在是太良心了,所以费时间给他们点赞一下~ SuperVessel云平台是IBM中国研究院和中国系统与技术中心基于POWER架构和OpenStack技术共同构建的, 支持开发者远程开发的免费 ...

  9. 嵌入式Linux引导过程之1.2——Xloader的XLOADER_ENTRY

    根据上文中获得的线索,本文分析init.S中的XLOADER_ENTRY. 在init.S中,定义了好多与平台相关的寄存器地址宏以及好多其他函数,我们在用到的时候再回过头来分析,这里,我们只看其中的一 ...

  10. R语言︱词典型情感分析文本操作技巧汇总(打标签、词典与数据匹配等)

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 笔者寄语:情感分析中对文本处理的数据的小技巧要 ...