之前听同事说在使用EventBus时为了防止eventbus在某一个监听者执行事件失败抛出异常导致其它监听者无法执行,必须使用try/catch显式捕获异常。但是今天在看源码时发现这样的说法是有问题的:eventbus不会因为某一个订阅者发生异常而终止后续订阅者的事件监听。下面是一段demo:
public class EventBusTest {
private static AtomicInteger increaser = new AtomicInteger(0);
public static void main(String[] args) {
AsyncEventBus bus = new AsyncEventBus(MoreExecutors.directExecutor());
bus.register(new EventBusReceiver());//注册eventbus事件监听者
EventBusEvent event = new EventBusEvent("eventbus event", increaser.incrementAndGet());
bus.post(event); //发布eventbus事件
}
}
public class EventBusEvent {
private String content;
private int id;
public EventBusEvent(String content, int id) {
super();
this.content = content;
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class EventBusReceiver {
@Subscribe
public void dealEventBusEvent(EventBusEvent event){
String content = event.getContent();
System.out.println("event bus发送过来的事件为:" + content);
throw new RuntimeException("故意抛出的异常");
}
@Subscribe
public void dealAnotherBusEvent(EventBusEvent event){
String content = event.getContent();
System.out.println("Second:" + content);
}
}
下面是上面main函数的执行结果:
可以看出我在第一个方法执行出错后并不会显式影响第二个订阅者。那么问题来了,
eventbus在针对某一种消息订阅的情况下不是只有一个线程吗?那为什么在执行出错抛出异常后不会影响其它订阅者呢?
以下是eventbus API说明:Subscribers should not, in general, throw. If they do, the EventBus will catch and log the exception. This is rarely the right solution for error handling and should not be relied upon; it is intended solely to help find problems during development.
就是说在订阅者方法体内最好不要抛出异常,如果抛了,eventbus会捕获到异常然后会将日子log下来。但是这种处理方式也是不可靠的,它只是用来在开发阶段发现问题而已。
下面从源码角度去揭开他的面纱!
首先eventbus会在构建时创建一个标示符、一个线程处理器、一个事件分发器、一个订阅者异常处理器。
每一个订阅者对应一个Subscriber,来看看这个类的构造:
private Subscriber(EventBus bus, Object target, Method method) {
this.bus = bus;
this.target = checkNotNull(target);
this.method = method;
method.setAccessible(true);
this.executor = bus.executor();
}
也就是说EventBus只需要记住哪些类的哪些方法,这样后面可以通过反射的方式去执行该方法。
再来看看监听器注册过程:
/**
* Registers all subscriber methods on the given listener object.
*/
void register(Object listener) {
Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);//guava中定义的map,支持一个key多个value
for (Map.Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
Class<?> eventType = entry.getKey();//获取到所有event class类型
Collection<Subscriber> eventMethodsInListener = entry.getValue(); //获取到所有订阅者
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);//CopyOnWriteArraySet是一个在多线程条件下对读的操作很快的一个set集合,最好不要做增加、删除或修改元素操作
if (eventSubscribers == null) {
CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
eventSubscribers =
MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);//返回两个参数中不为空的那个,如果都为空则抛出一个NullerPointException,若都不为空则返回第一个
}
eventSubscribers.addAll(eventMethodsInListener);
}
}
/**
* Returns all subscribers for the given listener grouped by the type of event they subscribe to.
*/
private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();//获取通过register注册的事件监听器对象
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();//拿到该事件监听器对象中的标有注解的方法中的所有参数类型
Class<?> eventType = parameterTypes[0];//拿到方法中的第一个参数,这个参数也就是event事件类型,如果在某一个标有@Subscribe的方法里面包含多个参数则会报错,该方法只能接收一个参数
methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
}
return methodsInListener;
}
findAllSubscribers方法的作用是找到某一种event所对应的所有订阅者(这里的订阅者是指所有标有@Subscribe注解的方法)。