EventBus是guava提供的适用于进程间的消息通信的一个工具,常常用于轻量级的异步数据同步场景。使用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的源码原理。
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);
}
/**
* 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);
}
}
事件注册的逻辑已写入代码注释中,主要为:
/**
* 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();
}
}
}
事件发送的逻辑如代码注释所示:
将事件订阅服务器方法标记为线程安全的。此注释表示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;
}