EventBus与Spring Event区别详解(EventBus 事件机制,Spring Event事件机制)

 更新时间:2020年02月07日 14:25:22   作者:漫夭  
这篇文章主要介绍了EventBus与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

适用场景:

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

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的事件机制更复杂,但是功能同样强大。

适用场景:

  1. 按照类区分事件
  2. 订阅 事件簇
  3. 支持自定义event
  4. 按照condition过滤同类型事件

比较EventBus与Spring Event

使用方式比较

项目 事件 发布者 发布方法 是否异步 监听者 注册方式
EventBus 任意对象 EventBus EventBus#post 注解Subscribe方法 手动注册EventBus#register
Spring Event 任意对象 ApplicationEventPublisher ApplicationEventPublisher#publishEvent 支持同步异步 注解EventListener方法 系统注册

使用场景比较

项目 事件区分 是否支持事件簇 是否支持自定义event 是否支持过滤 是否支持事件隔离 复杂程度
EventBus Class 简单
Spring Event Class 复杂

更多关于EventBus与Spring Event文章大家可查看下面的相关链接

相关文章

  • Java源码解析之可重入锁ReentrantLock

    Java源码解析之可重入锁ReentrantLock

    今天小编就为大家分享一篇关于Java源码解析之可重入锁ReentrantLock,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • java遍历途中修改数据及删除数据的方法总结

    java遍历途中修改数据及删除数据的方法总结

    在使用java的集合类遍历数据的时候,在某些情况下可能需要对某些数据进行删除,下面这篇文章主要给大家介绍了关于java遍历途中修改数据及删除数据的方法总结,需要的朋友可以参考下
    2023-10-10
  • Spring Boot MQTT Too many publishes in progress错误的解决方案

    Spring Boot MQTT Too many publishes in progress错误的解决方

    本文介绍Spring Boot MQTT Too many publishes in progress错误的解决方案,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-07-07
  • mybatis-plus配置日志两种实现方式

    mybatis-plus配置日志两种实现方式

    这篇文章主要给大家介绍了关于mybatis-plus配置日志两种实现方式的相关资料,Mybatis-plus集成了日志框架,可以将程序运行时产生的日志进行记录,方便开发人员进行问题排查,需要的朋友可以参考下
    2023-09-09
  • Idea servlet映射方法优缺点对比

    Idea servlet映射方法优缺点对比

    这篇文章主要介绍了Idea servlet映射方法优缺点对比,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Maven之远程仓库的配置详解

    Maven之远程仓库的配置详解

    这篇文章主要介绍了Maven之远程仓库的配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 剖析Java中线程编程的概念

    剖析Java中线程编程的概念

    这篇文章主要介绍了Java中线程编程的概念,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • IDEA 项目创建Mapper的xml文件的方法

    IDEA 项目创建Mapper的xml文件的方法

    这篇文章主要介绍了IDEA 项目创建Mapper的xml文件的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • java连接mysql数据库实现单条插入和批量插入

    java连接mysql数据库实现单条插入和批量插入

    这篇文章主要为大家详细介绍了java连接mysql数据库实现单条插入和批量插入,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • Java 实现协程的方法

    Java 实现协程的方法

    这篇文章主要介绍了Java 实现协程的方法,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-10-10

最新评论