EventBus源码赏析七 —— 问答

1,033 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情

创建

EventBus是否可以创建多个实例

通过源码分析,我们看到有一下几个地方可以获取实例

EventBus类

// 1
public EventBus() {
    this(DEFAULT_BUILDER);
}
// 2
public static EventBus getDefault() {
    EventBus instance = defaultInstance;
    if (instance == null) {
        synchronized (EventBus.class) {
            instance = EventBus.defaultInstance;
            if (instance == null) {
                instance = EventBus.defaultInstance = new EventBus();
            }
        }
    }
    return instance;
}

EventBusBuilder类

// 3
public EventBus installDefaultEventBus() {
    synchronized (EventBus.class) {
        if (EventBus.defaultInstance != null) {
            throw new EventBusException("Default instance already exists." +  " It may be only set once before it's used the first time to ensure consistent behavior.");
        }
        EventBus.defaultInstance = build();
        return EventBus.defaultInstance;
    }
}
// 4
public EventBus build() {
    return new EventBus(this);
}
  1. EventBus()使用默认参数创建了新的实例
  2. getDefault()使用单例获取EventBus的实例,每次获取的都是一致的
  3. installDefaultEventBus()会初始化EventBus类中的defaultInstance,也就是getDefault()获取的实例,但是它只能在defaultInstance为null时才能调用,否则会抛出异常
  4. 以上几种方法创建的实例都是使用的默认参数创建的,除此之外,EventBus支持通过构造者模式创建实例,通过EventBus.builder()获取EventBusBuilder对象,然后执行参数,最后调用build()方法创建EventBus的实例

所以EventBus支持创建多个实例。

注册

多次注册同一对象会能否注册成功

查看注册流程,来到subscribe()方法内部

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
		//是否重复注册
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }
	//省略其他代码
}

可以看到先通过订阅事件类型从subscriptionsByEventType中取出订阅信息集合,如果不为空则判断subscriptions.contains(newSubscription),Subscription是如何判断是否包含的呢,先看看他的equals()方法

public boolean equals(Object other) {
    if (other instanceof Subscription) {
        Subscription otherSubscription = (Subscription) other;
        return subscriber == otherSubscription.subscriber
                && subscriberMethod.equals(otherSubscription.subscriberMethod);
    } else {
        return false;
    }
}

判断中使用了subscriberMethod类的equals()方法

public boolean equals(Object other) {
    if (other == this) {
        return true;
    } else if (other instanceof SubscriberMethod) {
		//这里只展示了主流程,所以修改了部分源码
        SubscriberMethod otherSubscriberMethod = (SubscriberMethod)other;
        StringBuilder builder = new StringBuilder(64);
        builder.append(method.getDeclaringClass().getName());
        builder.append('#').append(method.getName());
        builder.append('(').append(eventType.getName());
        methodString = builder.toString();
        return methodString.equals(otherSubscriberMethod.methodString);
    } else {
        return false;
    }
}

由于篇幅问题,适当修改了下SubscriberMethod的equals()方法,阅读起来更清晰,可以看到,判断的核心在于判断拼接的字符串className#methodName(eventTypeName是否相等。 当我们重复注册的时候,这个结果一定是相等的,所以就会抛出EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "eventType)异常。

所以重复注册会抛出异常。可以调用isRegistered()判断是否注册过。

注册的时候哪些方法会被注册

我们以反射查找订阅方法为例,查看具体实现

private void findUsingReflectionInSingleClass(FindState findState) {
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
					if (findState.checkAdd(method, eventType)) {
						//省略其他代码
					}        
                }
            }
        }
    }
}

当获取到订阅类的所有方法后,首先对修饰符进行筛选

  1. (modifiers & Modifier.PUBLIC) != 0 方法必须是公开的
  2. (modifiers & MODIFIERS_IGNORE) == 0 (MODIFIERS_IGNORE=Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC) 方法必须是非静态的,非抽象的,以及不是编译过程中添加的,BRIDGE和SYNTHETIC不是Java的关键字,没有在公开的api公开。 然后对参数进行筛选(parameterTypes.length == 1)
  3. 所以方法必须只有一个参数 然后是注解(subscribeAnnotation != null)
  4. 方法必须有@Subscribe注解
  5. 最后是检查订阅方法是否已经添加过了,只有没添加过的才保留 所以只有满足以上5种条件的方法才会被注册

注册对象没有被 @Subscribe 注解的方法会如何

从上面得知,如果注册对象没被@Subscribe注解的方法,那么最后查找结果就是空数据,查看findUsingReflectionInSingleClass()方法的调用处

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
	//省略其他代码
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    }
}

当收集的订阅方法是空的时候,直接抛出EventBusException("Subscriber " + subscriberClass " and its super classes have no public methods with the @Subscribe annotation"异常) 所以注册对象没有被 @Subscribe 注解的方法会抛出异常

父类被@Subscribe注解的方法会被注册吗

还是以反射查找为例

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    while (findState.clazz != null) {
        findUsingReflectionInSingleClass(findState);
        findState.moveToSuperclass();
    }
}

可以看到订阅方法的解析是有层级的,先查找当前类符合条件的,再通过moveToSuperclass()查找上一级,直到findState.clazz为null

所以父类中被 @Subscribe 注解的方法有可能被注册。至于最后结果,还需要判断该方法是否满足上面说到的其余4种条件

事件

post时,如果没有找到 eventType 对应的注册方法会如何

查看发送事件部分的代码,最后定位到postSingleEvent()方法

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
	//省略部分
    boolean subscriptionFound = postSingleEventForEventType();
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

当事没有找到订阅方法的时候subscriptionFound=true,此时会根据logNoSubscriberMessages决定是否打印日志,而且如果当前事件类型不是NoSubscriberEventSubscriberExceptionEvent,并且配置了sendNoSubscriberEvent=true,则会发送一个NoSubscriberEvent事件。

粘性事件原理

粘性事件的实现比较简单,先在发送粘性事件之前将其保存一份,然后在注册完订阅者的时候如果发现粘性事件,就发送出去

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    post(event);
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
	//省略订阅等其他信息
    if (subscriberMethod.sticky) {
        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
    }
}

存储粘性事件stickyEvents的结构是ConcurrentHashMap,所以同一个粘性事件只会缓存最近一个。

反注册

多次反注册同一对象,或者反注册一个未被注册过的对象会如何?

public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //反注册
		typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

反注册一个存在的订阅者会将它从typesBySubscriber中移除,再次反注册subscribedTypes就会为null,直接打印日志。