Guava EventBus的具体使用以及源码解析
使用Guava EventBus对系统进行异步解耦改造
一、背景
最近在写的项目里,在使用定时器进行自动任务下派时,发现之前写的程序中将包括启动流程、地图更新、发送短信、效能计算等操作全部集成在同一个方法中同步执行,当任务量大时执行效率很低,且”一荣俱荣一损俱损“,即只要有一个操作执行失败,整条任务流程下派失败,可用性低。
为此使用Guava
的EventBus
组件对该模块代码进行重构,借此机会也对EventBus
进行了学习,并进行一个分享。分为以下几个部分:
- 简述:
Guava EventBus
的简单介绍 - 项目重构过程:存在问题以及解决方案代码分享
- 原理:
Guava EventBus
原理详解与源码解析 - 问题解答
- 总结
二、Guava EventBus简述
EventBus
是Guava
中的一个处理组件间通信的事件总线,是观察者模式的一种实现。相比于MQ
更加简洁,轻量,使事件生产者和事件消费者实现解耦分离。
1. 为什么使用事件总线?
当一个事件的发生(事件生产者),需要触发很多其他的事件(事件消费者)时,一般做法是在事件生产者中分别去调用其他事件,这样往往是很浪费资源的。事件生产者和事件消费者产生了极大的耦合。如果需要改动一个事件消费者,很可能还需要改动到事件生产者。
所以这个时候就可以用到事件总线了,利用EventBus
对事件进行统一管理,并使用异步的方式来发送事件。
2. 使用场景
在分布式场景下,组件与组件之间实现通信,使用异步的方式来发送事件或触发另一个动作,经常用到的框架是MQ
消息队列。如果是在同一个JVM
中进行事件通知的话,就可以使用到EventBus
,优点是简单、轻量、便捷。
3. 三个关键点
在这里放一张很经典的EventBus
结构图:
可以看到有三个关键组成部分:
Publisher
发布者事件发布者,就是将事件发送(
post
)到EventBus
事件总线的一方。事件发布者可以在程序的任何地方使用post方法将事件发送给
EventBus
事件总线,EventBus
再将事件发送给订阅者们。Event
事件Event
是EventBus
通信的基本单位,一个Event
可以是任何类型。简单来说,Event
就是Object
,可以将任意一个Bean
作为事件。Subscriber
订阅者事件订阅者,就是接受事件的一方,这些订阅者需要在自己的方法上添加
@Subscribe
注解来声明自己是一个事件订阅者,并将自己所在的类注册到EventBus
中,让EventBus
可以扫描到。
4. EventBus与AsyncEventBus
EventBus
是同步事件总线:
- 同步执行,事件发送方在发出事件之后,会等待所有的事件消费方执行完毕后,才会回来继续执行自己后面的代码。
- 事件发送方和事件消费方会在同一个线程中执行,消费方的执行线程取决于发送方。
- 同一个事件的多个订阅者,在接收到事件的顺序上面有不同。谁先注册到
EventBus
的,谁先执行,如果是在同一个类中的两个订阅者一起被注册到EventBus
的情况,收到事件的顺序跟方法名有关。
AsyncEventBus
是异步事件总线:
- 异步执行,事件发送方异步发出事件,不会等待事件消费方是否收到,直接执行自己后面的代码。
- 在定义
AsyncEventBus
时,构造函数中会传入一个线程池。事件消费方收到异步事件时,消费方会从线程池中获取一个新的线程来执行自己的任务。 - 同一个事件的多个订阅者,它们的注册顺序跟接收到事件的顺序上没有任何联系,都会同时收到事件,并且都是在新的线程中,异步并发的执行自己的任务。
5. SubscriberExceptionHandler
如果在发生异常时该怎么处理?
无论在EventBus
还是AsyncEventBus
都可以传入自定义的SubscriberExceptionHandler
。该handler会在出现异常时被调用,可以从Throwable
参数中获取到异常信息,从SubscriberExceptionContext
参数中获取到消息信息进行特定的处理。
SubscriberExceptionHandler
接口声明为:
public interface SubscriberExceptionHandler {
void handleException(Throwable var1, SubscriberExceptionContext var2);
}
我们可以实现这个接口实现自定义异常处理,如:
public class EventBusUtil implements SubscriberExceptionHandler {
@Override
public void handleException(Throwable throwable, SubscriberExceptionContext subscriberExceptionContext) {
log.error("eventBus handler exception", throwable);
}
}
三、项目消息通信重构过程
1. 存在问题
任务下派包含以下几个执行:
- 启动流程
- 位置落图
- 启动效能
- 发送短信
- 记录日志
- 以上所有执行都在一个方法内同步进行,任务量大时执行效率低;
- 所有执行强依赖,耦合度过高,只要有一个执行失败,整个方法失败,任务下派失败,可用性低。
2. 解决方案
采用观察者模式,对每个事件进行异步解耦,流程启动成功后发布消息,其他操作订阅消息。提高运行性能和可用性。
3. 具体重构过程
封装Guava
的EventBus
发布订阅事件总线处理组件实现。
- 在pom中引入guava包
<!-- Google Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
- 创建
EventBus
抽象类AbstractSpringEventBus
,实现ApplicationContextAware
接口,通过ApplicationContextAware
这个上下文环境得到Spring
容器的Bean
。这里是为了获取实现IEventConsumer
订阅接口的Bean
。
public abstract class AbstractSpringEventBus implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
scanConsumer(applicationContext);
}
/**
* 事件发布
*
* @param event 事件
*/
public abstract void post(Object event);
/**
* 注册
*
* @param event 事件
*/
public abstract void registerConsumer(Object event);
/**
* 取消注册
*
* @param event 事件
*/
public abstract void unregisterConsumer(Object event);
/**
* 扫描事件
*
*/
public void scanConsumer(ApplicationContext applicationContext) {
applicationContext.getBeansOfType(IEventConsumer.class).forEach((k, v) -> this.registerConsumer(v));
}
}
- 创建订阅者接口。之后所有需要进行消息订阅的消费者都需要实现此接口。
public interface IEventConsumer<T> {
/**
* 消费订阅事件
*
* @param t 实体
*/
void post(T t);
}
- 创建
EventBus
执行工具类。采用AsyncEventBus
调度器,指定线程池异步分发事件,同一个事件的多个订阅者,它们的注册顺序跟接收到事件的顺序上没有任何联系,都会同时收到事件,并且都是在新的线程中,异步并发的执行自己的任务。
@Slf4j
@Component
public class EventBusUtil extends AbstractSpringEventBus implements SubscriberExceptionHandler {
private final EventBus eventBus;
/**
* 创建线程池
*/
public EventBusUtil() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
20,
3000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy());
eventBus = new AsyncEventBus(executor, this);
}
@Override
public void post(Object event) {
eventBus.post(event);
}
@Override
public void registerConsumer(Object event) {
eventBus.register(event);
}
@Override
public void unregisterConsumer(Object event) {
eventBus.unregister(event);
}
@Override
public void handleException(Throwable throwable, SubscriberExceptionContext subscriberExceptionContext) {
log.error("eventBus handler exception", throwable);
}
}
- 创建订阅者,实现
IEventConsumer
接口。在方法上加上@Subscribe
注解,进行事件监听。
/**
* 订阅者 - 消息提醒操作
*
* @author huangyuqiu
* @version 1.0.0
* @date 2023/5/30 16:51
*/
@Slf4j
@Component
public class AppPushConsumer implements IEventConsumer<HcajEntity> {
@Resource
private HcajService hcajService;
@Subscribe
@Override
public void post(HcajEntity hcajEntity) {
hcajService.push(hcajEntity, BizTypeEnum.getByCode(hcajEntity.getBizType()));
}
}
/**
* 订阅者 - 地图更新操作
*
* @author huangyuqiu
* @version 1.0.0
* @date 2023/5/30 16:50
*/
@Slf4j
@Component
public class MapUpdateConsumer implements IEventConsumer<HcajEntity> {
@Resource
HcajMapUpdateLogService hcajMapUpdateLogService;
@Subscribe
@Override
public void post(HcajEntity hcajEntity) {
// 更新地图,并保存更新日志
hcajMapUpdateLogService.addToMap(hcajEntity);
}
}
/**
* 订阅者 - 效能计算
*
* @author huangyuqiu
* @version 1.0.0
* @date 2023/5/30 16:51
*/
@Slf4j
@Component
public class EffConsumer implements IEventConsumer<HcajEntity> {
@Resource
private EffUtil effUtil;
@Subscribe
@Override
public void post(HcajEntity hcajEntity) {
// 保存效能数据
effUtil.saveEff(hcajEntity.getActBusiness(), hcajEntity.getBizStartTime());
}
}
- 业务中执行操作,并进行消息发布
@Resource
private EventBusUtil eventBusUtil;
/**
* 业务执行操作
*/
protected void apply() {
// ... 省略部分代码 ...
// 自动发起流程
processService.apply();
// 异步消息发布:更新地图日志服务 / 保存效能数据服务 / 消息发送服务
eventBusPost(hcajEntity);
// ... 省略部分代码 ...
}
/**
* 异步消息发布:更新地图日志服务 / 保存效能数据服务 / 消息发送服务
*/
private void eventBusPost(HcajEntity hcajEntity) {
// 发布消息:更新地图日志服务 / 保存效能数据服务 / 消息发送服务
eventBusUtil.post(hcajEntity);
}
运行程序,当流程发起成功之后,可以看到更新地图日志服务、保存效能数据服务、消息发送服务成功订阅并消费了消息,异步解耦改造成功。
四、Guava EventBus原理详解与源码解析
在EventBus
中最重要的就是这么几个步骤:初始化、注册、发布、注销。接下来逐一解析EventBus
的源码。
先放一张UML
图。
可以看出其实关联挺少的。
1. 初始化
1.1. EventBus
EventBus
最主要最核心的初始化方法如下:
// EventBus的唯一标识,默认为default
private final String identifier;
// 线程处理器 默认是 MoreExecutors.directExecutor()
private final Executor executor;
// 异常处理机制
private final SubscriberExceptionHandler exceptionHandler;
// 消息分发队列 默认是 Dispatcher.perThreadDispatchQueue()
private final Dispatcher dispatcher;
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);
}
1.2. AsyncEventBus
查看AsyncEventBus
的源码可知,AsyncEventBus
继承自EventBus
,其构造函数最终还是调用的EventBus
的构造函数。如下:
@Beta
public class AsyncEventBus extends EventBus {
/**
* 创建一个新的AsyncEventBus,用于分配事件。identifier用于日志记录的总线名称
*
* @param identifier 总线的名称,用于日志记录。
* @param executor 用于分配事件的执行线程。在最后一个事件被发布到此事件总线之后,调用方有责任关闭执行器。
*/
public AsyncEventBus(String identifier, Executor executor) {
super(identifier, executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
}
/**
* 创建一个新的AsyncEventBus,用于分配事件
*
* @param executor 用于分配事件的执行线程。在最后一个事件被发布到此事件总线之后,调用方有责任关闭执行器。
* @param subscriberExceptionHandler 用于处理从订阅者那边抛出的异常处理
* @since 16.0
*/
public AsyncEventBus(Executor executor, SubscriberExceptionHandler subscriberExceptionHandler) {
super("default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler);
}
/**
* 创建一个新的AsyncEventBus,用于分配事件
*
* @param executor 用于分配事件的执行线程。在最后一个事件被发布到此事件总线之后,调用方有责任关闭执行器。
*/
public AsyncEventBus(Executor executor) {
super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
}
}
2. 注册(register)
EventBus
中使用register
方法对订阅者进行注册。源码如下:
/**
* 注册对象上的所有订阅者方法,以便接收事件
*
* @param object 该被注册的订阅方法所在对象
*/
public void register(Object object) {
subscribers.register(object);
}
可以看到是调用了 SubscriberRegistry
注册器的register
方法。
- 查看
register
方法:
void register(Object listener) {
// 按照所订阅的事件类型分组返回所有订阅者的订阅方法
Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
// 事件类型
Class<?> eventType = entry.getKey();
// 订阅者中的事件方法
Collection<Subscriber> eventMethodsInListener = entry.getValue();
// 获取当前事件类型的所有订阅者
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
if (eventSubscribers == null) {
CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
eventSubscribers =
MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
}
eventSubscribers.addAll(eventMethodsInListener);
}
}
可以看到在这个方法中,首先会去获取当前订阅者里的所有订阅方法,并根据事件类型(eventType
)分组后返回。之后遍历分组,根据事件类型将订阅的方法逐一加入 subscribers
注册器。
通过代码上下文可知,subscribers
注册器的本质其实就是一个 ConcurrentMap
:
/**
* 所有注册的订阅者,索引是事件类型
*
* <p>The {@link CopyOnWriteArraySet} values make it easy and relatively lightweight to get an
* immutable snapshot of all current subscribers to an event without any locking.
*/
private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers = Maps.newConcurrentMap();
注册本质上就是将订阅的方法加入到ConcurrentMap
中去。
另外,还可以看到 findAllSubscribers
方法,是将事件按照类型返回。方法源码如下:
/**
* 按照所订阅的事件类型分组返回所有订阅者的订阅方法
*/
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;
}
代码很好看懂,获取需要注册的类的带着订阅注解的方法,之后遍历分组。主要是 getAnnotatedMethods
方法。源码如下:
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
try {
return subscriberMethodsCache.getUnchecked(clazz);
} catch (UncheckedExecutionException e) {
throwIfUnchecked(e.getCause());
throw e;
}
}
可以看到其实是通过subscriberMethodsCache
这个缓存对象实例来获取。继续通过读上下文的代码,查看subscriberMethodsCache
的代码:
private static final LoadingCache<Class<?>, ImmutableList<Method>> subscriberMethodsCache =
CacheBuilder.newBuilder()
.weakKeys()
.build(
new CacheLoader<Class<?>, ImmutableList<Method>>() {
@Override
public ImmutableList<Method> load(Class<?> concreteClass) throws Exception {
return getAnnotatedMethodsNotCached(concreteClass);
}
});
重点关注getAnnotatedMethodsNotCached
方法。继续阅读getAnnotatedMethodsNotCached
的源码,如下:
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
for (Class<?> supertype : supertypes) {
for (Method method : supertype.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
// TODO(cgdecker): Should check for a generic parameter type and error out
Class<?>[] parameterTypes = method.getParameterTypes();
checkArgument(
parameterTypes.length == 1,
"Method %s has @Subscribe annotation but has %s parameters. "
+ "Subscriber methods must have exactly 1 parameter.",
method,
parameterTypes.length);
checkArgument(
!parameterTypes[0].isPrimitive(),
"@Subscribe method %s's parameter is %s. "
+ "Subscriber methods cannot accept primitives. "
+ "Consider changing the parameter to %s.",
method,
parameterTypes[0].getName(),
Primitives.wrap(parameterTypes[0]).getSimpleName());
MethodIdentifier ident = new MethodIdentifier(method);
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, method);
}
}
}
}
return ImmutableList.copyOf(identifiers.values());
}
到这里就可以很清楚的看到,这个方法中获取了带Subscribe注解的方法,并将它封装进ImmutableList
这个集合中并返回,加入到缓存中。之后在方法中获取缓存里已经存在的订阅方法,注册进ConcurrentMap
。
至此,注册方法的源码解析完毕。
3. 注销(unregister)
解析完注册的源码,那么注销的源码就很简单了,就是将订阅方法从ConcurrentMap
中删除。源码如下:
void unregister(Object listener) {
Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
Class<?> eventType = entry.getKey();
Collection<Subscriber> listenerMethodsForType = entry.getValue();
CopyOnWriteArraySet<Subscriber> currentSubscribers = subscribers.get(eventType);
if (currentSubscribers == null || !currentSubscribers.removeAll(listenerMethodsForType)) {
// if removeAll returns true, all we really know is that at least one subscriber was
// removed... however, barring something very strange we can assume that if at least one
// subscriber was removed, all subscribers on listener for that event type were... after
// all, the definition of subscribers on a particular class is totally static
throw new IllegalArgumentException(
"missing event subscriber for an annotated method. Is " + listener + " registered?");
}
// don't try to remove the set if it's empty; that can't be done safely without a lock
// anyway, if the set is empty it'll just be wrapping an array of length 0
}
}
如果删除之后返回true
,表示至少有一个订阅者被注销了。
注销的代码很简单,就不多做探究。
4. 发布(post)
注册完订阅者之后,就可以在想要发布的地方使用post
来发布消息了。post
的源码如下:
/**
* 给所有注册过的订阅者发布事件。这个方法会在事件发布给所有订阅者之后成功返回,
* 不管订阅者们抛出了什么异常。
*
* 如果没有订阅者订阅当前event事件,且该event事件不是DeadEvent,它将会被封装为DeadEvent之后重新发布。
*
* @param event 需要发布的事件
*/
public void post(Object event) {
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
// the event had no subscribers and was not itself a DeadEvent
post(new DeadEvent(this, event));
}
}
从上述代码中可以看到,先使用SubscriberRegistry
的实例对象获取当前事件类型的所有订阅者,并返回一个迭代器。如果存在订阅者则调用dispatcher.dispatch
方法,否则作为DeadEvent
类型重新进行发布。
DeadEvent
是比较特殊的事件类型,注释源码是这么定义的:Wraps an event that was posted, but which had no subscribers and thus could not be delivered.
Registering a DeadEvent subscriber is useful for debugging or logging, as it can detect misconfigurations in a system's event distribution.
解释一下说就是,
DeadEvent
用于封装因为没有订阅者而无法送达的事件。注册DeadEvent
时间对于调试和日志记录是很有用的,因为它可以检测一些系统事件分派的错误配置问题。
Dispatcher
是将事件分派给订阅者的处理程序,是一个抽象类,不同的EventBus
会调用不同类的dispatch
实现。
EventBus
使用的是PerThreadQueuedDispatcher
类的实现,AsyncEventBus
使用的是LegacyAsyncDispatcher
类的实现。我们一一来看。
4.1.1. PerThreadQueuedDispatcher
EventBus
默认情况下Dispatcher
实现的都是PerThreadQueuedDispatcher
类
private static final class PerThreadQueuedDispatcher extends Dispatcher {
// 这是 EventBus 默认的转发器,可以翻译作:"每个线程单独设置一个队列"转发器。
/** 事件调度的线程队列。定义一个 ThreadLocal 线程私有对象,每次获取的时候都能够获得一个队列 */
private final ThreadLocal<Queue<Event>> queue =
new ThreadLocal<Queue<Event>>() {
@Override
protected Queue<Event> initialValue() {
return Queues.newArrayDeque();
}
};
/** 线程私有对象,用于保存每个线程的转发状态,防止事件被重复转发 */
private final ThreadLocal<Boolean> dispatching =
new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
checkNotNull(subscribers);
// 获取线程私有的队列
Queue<Event> queueForThread = queue.get();
// 往队列写入需要被转发的 Event(事件本身+监听者们)
queueForThread.offer(new Event(event, subscribers));
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();
}
}
}
上面的代码很好理解,先检查事件和订阅者迭代器是否为空,之后将事件打包放入队列中,判断当前线程是否被调度如,如果没有,则从队列中获取事件,执行订阅者类subscribers
的dispatchEvent
方法。
dispatchEvent
就是真正执行事件的方法,源码如下:
/** Dispatches {@code event} to this subscriber using the proper executor. */
final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
}
/**
* Invokes the subscriber method. This method can be overridden to make the invocation
* synchronized.
*/
@VisibleForTesting
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
try {
method.invoke(target, checkNotNull(event));
} catch (IllegalArgumentException e) {
throw new Error("Method rejected target/argument: " + event, e);
} catch (IllegalAccessException e) {
throw new Error("Method became inaccessible: " + event, e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof Error) {
throw (Error) e.getCause();
}
throw e;
}
}
可以看到,dispatchEvent
方法其实就是通过反射来执行了订阅方法。
4.1.2. LegacyAsyncDispatcher
如果使用的是AsyncEventBus
,那么就会走LegacyAsyncDispatcher
类的方法实现。
/** Implementation of a {@link #legacyAsync()} dispatcher. */
private static final class LegacyAsyncDispatcher extends Dispatcher {
// 在多线程环境下无法保证事件执行的顺序性。
/** 全局事件队列 */
private final ConcurrentLinkedQueue<EventWithSubscriber> queue =
Queues.newConcurrentLinkedQueue();
@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
while (subscribers.hasNext()) {
queue.add(new EventWithSubscriber(event, subscribers.next()));
}
EventWithSubscriber e;
while ((e = queue.poll()) != null) {
e.subscriber.dispatchEvent(e.event);
}
}
private static final class EventWithSubscriber {
private final Object event;
private final Subscriber subscriber;
private EventWithSubscriber(Object event, Subscriber subscriber) {
this.event = event;
this.subscriber = subscriber;
}
}
}
LegacyAsyncDispatcher
的代码与PerThreadQueuedDispatcher
的区别不是很大,最终也是调用的dispatchEvent
方法反射执行订阅方法,区别在于LegacyAsyncDispatcher
使用了线程安全队列,以及可以自定义多线程环境。
五、总结
在项目中存在一些需要进行异步解耦处理事件的场景时,Guava
的EventBus
就能很好的派上用场,且其源码十分优雅漂亮,用到了很多设计模式,API
简单,使用也很简单,是一个很好的可供学习的工具。
Guava EventBus的具体使用以及源码解析的更多相关文章
- EventBus源码解析 源码阅读记录
EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...
- 【Android】EventBus 源码解析
EventBus 源码解析 本文为 Android 开源项目实现原理解析 中 EventBus 部分项目地址:EventBus,分析的版本:ccc2771,Demo 地址:EventBus Demo分 ...
- EventBus (三) 源码解析 带你深入理解EventBus
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...
- Android EventBus源码解析 带你深入理解EventBus
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...
- Google guava cache源码解析1--构建缓存器(1)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHas ...
- 源码解析-EventBus
示例使用 时序图 源码解读 EventBus 使用 官网定义:EventBus 是一个使用 Java 写的观察者模式,解耦的 Android 开源库.EventBus 只需要几行代码即可解耦简化代码, ...
- 第二章 Google guava cache源码解析1--构建缓存器
1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能) ...
- 设计模式课程 设计模式精讲 7-3 建造者模式源码解析(jdk+guava+spring+mybaties)
1 源码解析 1.1 jdk解析 1.2 guava解析 1.3 spring解析 1.4 mybaties解析 1 源码解析 1.1 jdk解析 String public StringBuilde ...
- andorid jar/库源码解析之EventBus
目录:andorid jar/库源码解析 EventBus: 作用: 用于不同Activity,Service等之间传递消息(数据). 栗子: A页面:onCreate定义 EventBus.ge ...
- EventBus3.0源码解析
本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充 ...
随机推荐
- freeswitch on debian docker
概述 freeswitch是一款简单好用的VOIP开源软交换平台. 因为centos系统期限的原因,尝试在debian的docker上使用fs. 环境 docker engine:Version 24 ...
- C#语言 十大经典排序算法动画与解析!(动态演示+代码)(java改写成C# )
以下内容是根据 https://www.cnblogs.com/fivestudy/p/10212306.html 进行改写 排序算法是<数据结构与算法>中最基本的算法之一. 排序算法 ...
- C++ 不使用虚析构的后果及分析
很多 C++ 方面的书籍都说明了虚析构的作用: 保证派生类的析构函数被调用,并且使析构顺序与构造函数相反 保证资源能够被正确释放 很久一段时间以来,我一直认为第 2 点仅仅指的是:当派生类使用 RAI ...
- maven总结二: 常用标签及属性
本文为博主原创,未经允许不得转载 目录: 1. maven 依赖属性:groupId.artifactId.version 2.插件执行: execution,id ,phase,goals,con ...
- 260. 只出现一次的数字 III
1.题目介绍 2.题解 2.1 快排+遍历 思路 同本系列前几题一样 代码 class Solution { public: std::vector<int> singleNumber(s ...
- 【MicroPython】生成micropython版本头文件 - py\makeversionhdr.py
用法 $ python makeversionhdr.py mpversion.h 实现 带git仓 get_version_info_from_git 使用git指令: git describe ...
- [转帖] q命令-用SQL分析文本文件
https://www.cnblogs.com/codelogs/p/16060830.html 简介# 在Linux上分析文本文件时,一般会使用到grep.sed.awk.sort.uniq等命令, ...
- MySQL数据库页存储结构学习与了解
MySQL数据库页存储结构学习与了解 背景 MySQL总是出现奇奇怪怪的问题. 想着自己能够学习与提高一下. 最近看了很多文档.关于MySQL数据库相关的. 想着总结和提炼一下, 希望能够给未来的工作 ...
- [转帖]PyCharm无法安装第三方模块,一直提示 updating list:time out 解决办法
Pycharm无法安装第三方模块解决办法: 1.打开pycharm的项目的venv文件夹 2.打开文件夹目录中的pyvenv文件 3.将文件中的include-system-site-packages ...
- Stream的简单学习
Stream的简单学习 前言 https://github.com/jeffhammond/STREAM unzip STREAM-master.zip cd /STREAM-master/ make ...