如何检测Guava EventBus是否存在特定事件的订阅者?
嘿,这个场景我太熟悉了!Guava EventBus本身确实没有提供直接查询特定事件订阅者数量的公开API,但咱们可以通过两种方式来实现这个需求,帮你在广播事件前检测是否有订阅者,避免漏注册的问题:
方案一:利用Guava内部的SubscriberRegistry(反射实现)
Guava的EventBus其实是通过SubscriberRegistry这个包私有内部类来管理所有订阅者映射的,我们可以通过反射调用它的内部方法来获取指定事件的订阅者集合:
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.SubscriberRegistry; import java.lang.reflect.Method; import java.util.Set; public class EventBusSubscriberChecker { // 检测指定事件是否存在订阅者 public static boolean hasSubscribers(EventBus eventBus, Class<?> eventClass) { try { // 获取EventBus内部的SubscriberRegistry实例 Method getRegistryMethod = EventBus.class.getDeclaredMethod("subscriberRegistry"); getRegistryMethod.setAccessible(true); SubscriberRegistry registry = (SubscriberRegistry) getRegistryMethod.invoke(eventBus); // 获取该事件对应的所有订阅者 Method getSubscribersMethod = SubscriberRegistry.class.getDeclaredMethod("getSubscribers", Object.class); getSubscribersMethod.setAccessible(true); Set<?> subscribers = (Set<?>) getSubscribersMethod.invoke(registry, eventClass); return !subscribers.isEmpty(); } catch (Exception e) { // 这里可以换成你自己的日志记录逻辑 e.printStackTrace(); return false; } } }
注意事项:
- 这个方案依赖Guava的内部API,不同版本的Guava可能会调整
SubscriberRegistry的方法或结构,升级Guava版本后记得测试这段代码是否正常工作。 - 反射会带来一定的性能开销,如果是高频调用场景,建议谨慎使用。
方案二:自定义可追踪的EventBus(更稳定)
如果不想依赖内部API,更稳妥的方式是自己封装一个EventBus,在注册/取消订阅时主动维护事件与订阅者的映射关系:
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class TrackedEventBus extends EventBus { // 记录每个事件类型的订阅者数量 private final Map<Class<?>, Integer> eventSubscriberCount = new HashMap<>(); @Override public void register(Object subscriber) { super.register(subscriber); // 扫描订阅者类中所有带@Subscribe注解的方法 for (Method method : subscriber.getClass().getDeclaredMethods()) { if (method.isAnnotationPresent(Subscribe.class)) { // 订阅方法的第一个参数就是事件类型 Class<?> eventType = method.getParameterTypes()[0]; eventSubscriberCount.put(eventType, eventSubscriberCount.getOrDefault(eventType, 0) + 1); } } } @Override public void unregister(Object subscriber) { super.unregister(subscriber); // 取消订阅时同步更新计数 for (Method method : subscriber.getClass().getDeclaredMethods()) { if (method.isAnnotationPresent(Subscribe.class)) { Class<?> eventType = method.getParameterTypes()[0]; int currentCount = eventSubscriberCount.getOrDefault(eventType, 0) - 1; if (currentCount <= 0) { eventSubscriberCount.remove(eventType); } else { eventSubscriberCount.put(eventType, currentCount); } } } } // 公开方法:检测指定事件是否有订阅者 public boolean hasSubscribers(Class<?> eventClass) { return eventSubscriberCount.containsKey(eventClass) && eventSubscriberCount.get(eventClass) > 0; } }
优势:
- 完全基于Guava的公开API实现,版本兼容性更好,不用担心Guava升级导致代码失效。
- 性能开销极小,只是在注册/取消时做简单的反射扫描,广播事件前的查询是O(1)的。
使用建议
- 如果只是临时排查问题或者做一次性的日志检测,方案一可以快速实现;
- 如果是长期在生产环境使用,优先选择方案二,稳定性和可维护性更强。
比如在广播事件前,你可以这样写:
TrackedEventBus eventBus = new TrackedEventBus(); // ... 注册订阅者 MyEvent event = new MyEvent(); if (!eventBus.hasSubscribers(MyEvent.class)) { // 记录警告日志 log.warn("事件{}没有任何订阅者,请检查是否漏注册或添加@Subscribe注解", MyEvent.class.getName()); } eventBus.post(event);
内容的提问来源于stack exchange,提问作者Tinus Tate
相关产品推荐
相关产品推荐

