Guava异步事件EventBus解析

EventBus源码分析

EventBus是guava提供的适用于进程间的消息通信的一个工具,常常用于轻量级的异步数据同步场景。使用EventBus可以使得业务逻辑解耦,并进行公用逻辑的抽象,一定程度上精简了业务代码。

EventBus使用说明

1)引入依赖

2)编写工具类如下:

import com.google.common.eventbus.EventBus;
import org.springframework.stereotype.Component;

@Component
@SuppressWarnings("UnstableApiUsage")
public final class EventCenter {

    private final static EventBus EVENT_BUS = new EventBus();

    private EventCenter() {

    }

    public static void register(Object obj) {
        EVENT_BUS.register(obj);
    }

    public static void unregister(Object obj) {
        EVENT_BUS.unregister(obj);
    }

    public static void post(Object obj) {
        EVENT_BUS.post(obj);
    }
}

3)注册事件

在需要发出事件的类上注册,代码如下:

@PostConstruct
public void init() {
    EventCenter.register(this);
}

@PostConstruct注解的作用是保证void()方法中的代码在servlet启动时执行,并且只会执行一次。

4)定义事件类

编写一个事件通知类,成员属性常有事件类型,对象id等

5)发出事件

当一件事件触发后,我们可以发出一个事件类,以供异步处理,具体代码如下:

ChangeNotify notify = ChangeNotify.builder().id(id)
        .changeType(type)
        .build();
EventCenter.post(notify);

6)处理事件通知

当我们发出一个事件后,就可以在一个代码中进行后续处理,代码如下:

@AllowConcurrentEvents
@Subscribe
void on(ChangeNotify event) {
    EventBusEnum changeType = event.getChangeType();
    long uid = event.getUid();
    int type = event.getType();
    
    ...
        
    userMapper.insert(uid,type);
    
    ...
}

至此,便走完了EventBus的整个流程,并不难,只要根据业务场景考虑好需要事件类的属性即可。接下来我们分析下EventBus的源码原理。

EventBus源码分析

成员属性

private static final Logger logger = Logger.getLogger(EventBus.class.getName());

private final String identifier;
private final Executor executor;
private final SubscriberExceptionHandler exceptionHandler;

private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
private final Dispatcher dispatcher;

public EventBus(SubscriberExceptionHandler exceptionHandler) {
    this(
        "default",
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        exceptionHandler);
}

EventBus(
   String identifier,
   Executor executor,
   Dispatcher dispatcher,
   SubscriberExceptionHandler exceptionHandler) {
	this.identifier = checkNotNull(identifier);
	this.executor = checkNotNull(executor);
	this.dispatcher = checkNotNull(dispatcher);
	this.exceptionHandler = checkNotNull(exceptionHandler);
}
  • identifier为事件标识,默认为default;
  • executor为directExecutor,返回guava默认的Executor,执行回调方法不会新开线程,所有回调方法都在当前线程做;
  • exceptionHandler负责处理订阅异常;
  • subscribers为订阅者列表;
  • dispatcher为事件调度器,确保同个EventBus各个事件的处理顺序与发送顺序保持一致。

事件注册

/**
* Registers all subscriber methods on {@code object} to receive events.
*/
public void register(Object object) {
  subscribers.register(object);
}

void register(Object listener) {
    // 1 获取监听此对象的所有带注解的方法map
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);

    for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
      Class<?> eventType = entry.getKey();
      Collection<Subscriber> eventMethodsInListener = entry.getValue();

      // 2 获取class中所有的subscribers
      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);

      // 3 内存中还没有此类的记录,添加一个new set
      if (eventSubscribers == null) {
        CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
        eventSubscribers =
            MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
      }

      // 4 内存中已有此记录,直接叠加即可
      eventSubscribers.addAll(eventMethodsInListener);
    }
}

事件注册的逻辑已写入代码注释中,主要为:

  1. 获取监听此对象的所有带注解的方法map;
  2. 遍历class,获取所有的subscribers;
  3. 如果内存中还没有此类的记录,添加一个new set;
  4. 内存中已有此记录,直接叠加即可。

事件发送

/**
* Posts an event to all registered subscribers. This method will return 
* successfully after the event has been posted to all subscribers, and 
* regardless of any exceptions thrown by subscribers.
*/
public void post(Object event) {
    // 1 获取所有监听此事件的订阅者
	Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
    if (eventSubscribers.hasNext()) {
        // 2 给这些订阅者发送事件
    	dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
    	// 3 新建死事件,将其加入
    	post(new DeadEvent(this, event));
    }
}

@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
	checkNotNull(event);
	checkNotNull(subscribers);
    // 4 获取当前线程的事件队列(queue类型为ThreadLocal),并添加新增的事件
	Queue<Event> queueForThread = queue.get();
	queueForThread.offer(new Event(event, subscribers));

    // 5 如果还没发送,依次将事件队列顺序发送
	if (!dispatching.get()) {
        
   		dispatching.set(true);
        try {
        	Event nextEvent;
        	while ((nextEvent = queueForThread.poll()) != null) {
            	while (nextEvent.subscribers.hasNext()) {
            		nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
            	}
          	}
        } finally {
        	dispatching.remove();
        	queue.remove();
        }
    }
}

事件发送的逻辑如代码注释所示:

  1. 获取所有监听此事件的订阅者;
  2. 如果有订阅者,给这些订阅者发送事件;
  3. 如果没有订阅者,创建死事件类;
  4. 获取当前线程的事件队列(queue类型为ThreadLocal),并添加新增的事件;
  5. 如果还没发送,依次将事件队列顺序发送。

注解说明

  • @AllowConcurrentEvents

将事件订阅服务器方法标记为线程安全的。此注释表示EventBus可以从多个线程同时调用事件订阅服务器。

  • @Subscribe

    事件的类型将由方法的第一个(也是唯一的)参数指示。如果此注释应用于具有零参数或多个参数的方法,则包含该方法的对象将无法注册以从EventBus进行事件传递。

    如果不用allowconcurrenentevents注释,事件订阅方法将由注册它们的每个事件总线串行调用。

注解解析

在事件注册代码中,有一个findAllSubscribers()方法,它是EventBus用来识别上述注解的,源码如下:

private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
    Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
    Class<?> clazz = listener.getClass();
    // 获取带有上述注解的方法
    for (Method method : getAnnotatedMethods(clazz)) {
        // 获取方法参数
    	Class<?>[] parameterTypes = method.getParameterTypes();
    	Class<?> eventType = parameterTypes[0];
    	methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
    }
    return methodsInListener;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值