EventBus與Spring Event區(qū)別詳解(EventBus 事件機(jī)制,Spring Event事件機(jī)制)
本地異步處理,采用事件機(jī)制 可以使 代碼解耦,更易讀。事件機(jī)制實(shí)現(xiàn)模式是 觀察者模式(或發(fā)布訂閱模式),主要分為三部分:發(fā)布者、監(jiān)聽(tīng)者、事件。
Guava EventBus
Guava EventBus實(shí)現(xiàn)是觀察者模式,用法很簡(jiǎn)單,先上代碼。
/**
* Desc: 事件對(duì)象
*/
@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: 事件監(jiān)聽(tīng)器,可以監(jiān)聽(tīng)多個(gè)事件。處理方法添加 @Subscribe 注解即可。
*/
public class GeventListener {
/**
* 監(jiān)聽(tīng) HelloEvent 類型及其父類型(Object)的事件
*/
@Subscribe
public void processEvent(HelloEvent event){
System.out.println("process hello event, name:" + event.getEventName());
}
/**
* 監(jiān)聽(tīng) WorldEvent 類型及其父類型(HelloEvent 和 Object)的事件
*/
@Subscribe
public void processWorldEvent(WorldEvent event) {
System.out.println("process world eventV1, no:" + event.getEventNo() + ", name:" + event.getEventName());
}
/**
* 注冊(cè)多個(gè)監(jiān)聽(tīng)器 監(jiān)聽(tīng)同一事件
* @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));
}
}
結(jié)果如下:
//HelloEvent被兩個(gè)監(jiān)聽(tīng)器處理(HelloEvent類及Object類的監(jiān)聽(tīng)器) process hello event, name:hello process common event, class:HelloEvent //WorldEvent被四個(gè)監(jiān)聽(tīng)器處理(兩個(gè)自己的,兩個(gè)父類的) 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把類當(dāng)做事件,是以class為key注冊(cè)和管理事件的,value是事件監(jiān)聽(tīng)器的method;事件監(jiān)聽(tīng)器只處理某一類(及其父類)事件。
事件注冊(cè)與發(fā)布
//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 {
//找到所有事件訂閱者(事件監(jiān)聽(tīng)器)
Set<EventSubscriber> wrappers = subscribersByType.get(eventType);
if (!wrappers.isEmpty()) {
dispatched = true;
for (EventSubscriber wrapper : wrappers) {
//事件入隊(duì)列
enqueueEvent(event, wrapper);
}
}
} finally {
subscribersByTypeLock.readLock().unlock();
}
}
//如果沒(méi)有訂閱者訂閱此類消息,則為 DeadEvent
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();
}
事件隔離
多個(gè)EventBus可以隔離事件。
public class AnotherListener {
/**
* 監(jiān)聽(tīng) 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));
}
}
結(jié)果是
//eventBus結(jié)果與之前相同 process hello event, name:hello //anotherEventBus 發(fā)布的事件,只被其注冊(cè)的監(jiān)聽(tīng)器處理 process common event, class:HelloEvent process another world event, no:666, name:AnotherWorld
適用場(chǎng)景:
- 按照類區(qū)分事件
- 訂閱 事件簇
- 支持自定義event,可以根據(jù)event自己寫(xiě)分發(fā)器
- 事件隔離
spring event
spring 新版事件機(jī)制也比較簡(jiǎn)單,看代碼。
/**
* 繼承 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;
}
/**
* 監(jiān)聽(tīng)器類,spring也支持一個(gè)類中監(jiān)聽(tīng)多個(gè)事件
*/
@Component("springListener")
public class SpringListener {
/**
* 監(jiān)聽(tīng)所有ApplicationEvent類型 及其子類型 的事件
*/
@EventListener
public void processApplicationEvent(ApplicationEvent event) {
System.out.println("process common event, class:" + event.getClass().getSimpleName());
}
/**
* 監(jiān)聽(tīng) HelloEvent類型 事件
*/
@EventListener
public void processHelloEvent(HelloEvent event) {
System.out.println("process helloEvent, name:" + event.getEventName());
}
/**
* 監(jiān)聽(tīng) CustomerEvent 類型事件,但是需要滿足condition條件,即isCustomer=true
*/
@EventListener(condition = "#event.isCustomer")
public void processCustomerEvent(CustomerEvent event) {
System.out.println("process customer CustomerEvent, name:" + event.getName());
}
/**
* 監(jiān)聽(tīng) 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());
}
}
//執(zhí)行類,測(cè)試入口
@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));
}
}
結(jié)果是
//以下是spring上下文event,繼承自 ApplicationContextEvent。 用于用戶參與上下文生命周期的入口。因?yàn)槭茿pplicationEvent子類型,所以,由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,會(huì)被processApplicationEvent處理 process common event, class:HelloEvent //監(jiān)聽(tīng) HelloEvent類型 的 processHelloEvent 處理 process helloEvent, name:helloEvent //非 ApplicationEvent 的事件,則為 PayloadApplicationEvent process common event, class:PayloadApplicationEvent //isCustomer=true,符合processCustomerEvent處理?xiàng)l件 process customer CustomerEvent, name:customer //監(jiān)聽(tīng)CustomerEvent類型,處理結(jié)果 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上下文事件??梢酝ㄟ^(guò)監(jiān)聽(tīng)這些事件,參與到spring生命周期中去。這種無(wú)侵入性交互方式,在做平臺(tái)服務(wù)時(shí),是一種很好的方式。
注冊(cè)監(jiān)聽(tīng)器
org.springframework.context.event.EventListenerMethodProcessor#processBean 將所有注解EventListener的方法,存入上下文的applicationListeners中。Listener的封裝類為ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method)。
org.springframework.context.support.AbstractApplicationContext#refresh 中調(diào)用 initApplicationEventMulticaster 初始化事件發(fā)布管理器applicationEventMulticaster,然后調(diào)用registerListeners() 注冊(cè)監(jiān)聽(tīng)器。
發(fā)布事件
spring 起初只支持 ApplicationEvent類型事件,后來(lái)優(yōu)化之后,支持自定義事件。自定義事件的處理,默認(rèn)為PayloadApplicationEvent,相當(dāng)于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);
}
//調(diào)用父類,發(fā)布事件
// 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);
}
}
}
執(zhí)行事件
@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//獲取事件的監(jiān)聽(tīng)器集合,并逐個(gè)觸發(fā)執(zhí)行監(jiān)聽(tīng)器
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//異步的話,就放在線程池中執(zhí)行
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
//本線程調(diào)用
invokeListener(listener, event);
}
}
}
可以看到,spring的事件機(jī)制更復(fù)雜,但是功能同樣強(qiáng)大。
適用場(chǎng)景:
- 按照類區(qū)分事件
- 訂閱 事件簇
- 支持自定義event
- 按照condition過(guò)濾同類型事件
比較EventBus與Spring Event
使用方式比較
| 項(xiàng)目 | 事件 | 發(fā)布者 | 發(fā)布方法 | 是否異步 | 監(jiān)聽(tīng)者 | 注冊(cè)方式 |
|---|---|---|---|---|---|---|
| EventBus | 任意對(duì)象 | EventBus | EventBus#post | 是 | 注解Subscribe方法 | 手動(dòng)注冊(cè)EventBus#register |
| Spring Event | 任意對(duì)象 | ApplicationEventPublisher | ApplicationEventPublisher#publishEvent | 支持同步異步 | 注解EventListener方法 | 系統(tǒng)注冊(cè) |
使用場(chǎng)景比較
| 項(xiàng)目 | 事件區(qū)分 | 是否支持事件簇 | 是否支持自定義event | 是否支持過(guò)濾 | 是否支持事件隔離 | 復(fù)雜程度 |
|---|---|---|---|---|---|---|
| EventBus | Class | 是 | 是 | 否 | 是 | 簡(jiǎn)單 |
| Spring Event | Class | 是 | 是 | 是 | 否 | 復(fù)雜 |
更多關(guān)于EventBus與Spring Event文章大家可查看下面的相關(guān)鏈接
相關(guān)文章
java遍歷途中修改數(shù)據(jù)及刪除數(shù)據(jù)的方法總結(jié)
在使用java的集合類遍歷數(shù)據(jù)的時(shí)候,在某些情況下可能需要對(duì)某些數(shù)據(jù)進(jìn)行刪除,下面這篇文章主要給大家介紹了關(guān)于java遍歷途中修改數(shù)據(jù)及刪除數(shù)據(jù)的方法總結(jié),需要的朋友可以參考下2023-10-10
Spring?Boot?MQTT?Too?many?publishes?in?progress錯(cuò)誤的解決方
本文介紹Spring?Boot?MQTT?Too?many?publishes?in?progress錯(cuò)誤的解決方案,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-07-07
mybatis-plus配置日志兩種實(shí)現(xiàn)方式
這篇文章主要給大家介紹了關(guān)于mybatis-plus配置日志兩種實(shí)現(xiàn)方式的相關(guān)資料,Mybatis-plus集成了日志框架,可以將程序運(yùn)行時(shí)產(chǎn)生的日志進(jìn)行記錄,方便開(kāi)發(fā)人員進(jìn)行問(wèn)題排查,需要的朋友可以參考下2023-09-09
Idea servlet映射方法優(yōu)缺點(diǎn)對(duì)比
這篇文章主要介紹了Idea servlet映射方法優(yōu)缺點(diǎn)對(duì)比,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
Maven之遠(yuǎn)程倉(cāng)庫(kù)的配置詳解
這篇文章主要介紹了Maven之遠(yuǎn)程倉(cāng)庫(kù)的配置詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
IDEA 項(xiàng)目創(chuàng)建Mapper的xml文件的方法
這篇文章主要介紹了IDEA 項(xiàng)目創(chuàng)建Mapper的xml文件的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
java連接mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)單條插入和批量插入
這篇文章主要為大家詳細(xì)介紹了java連接mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)單條插入和批量插入,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08

