EventBus.java revision 1d580d0f6ee4f21eb309ba7b509d2c6d671c4044
1/* 2 * Copyright (C) 2007 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.common.eventbus; 18 19import com.google.common.annotations.Beta; 20import com.google.common.annotations.VisibleForTesting; 21import com.google.common.base.Supplier; 22import com.google.common.base.Throwables; 23import com.google.common.cache.CacheBuilder; 24import com.google.common.cache.CacheLoader; 25import com.google.common.cache.LoadingCache; 26import com.google.common.collect.Lists; 27import com.google.common.collect.Multimap; 28import com.google.common.collect.Multimaps; 29import com.google.common.collect.SetMultimap; 30import com.google.common.collect.Sets; 31 32import java.lang.reflect.InvocationTargetException; 33import java.util.Collection; 34import java.util.List; 35import java.util.Map.Entry; 36import java.util.Set; 37import java.util.concurrent.ConcurrentHashMap; 38import java.util.concurrent.ConcurrentLinkedQueue; 39import java.util.concurrent.CopyOnWriteArraySet; 40import java.util.concurrent.ExecutionException; 41import java.util.logging.Level; 42import java.util.logging.Logger; 43 44/** 45 * Dispatches events to listeners, and provides ways for listeners to register 46 * themselves. 47 * 48 * <p>The EventBus allows publish-subscribe-style communication between 49 * components without requiring the components to explicitly register with one 50 * another (and thus be aware of each other). It is designed exclusively to 51 * replace traditional Java in-process event distribution using explicit 52 * registration. It is <em>not</em> a general-purpose publish-subscribe system, 53 * nor is it intended for interprocess communication. 54 * 55 * <h2>Receiving Events</h2> 56 * To receive events, an object should:<ol> 57 * <li>Expose a public method, known as the <i>event handler</i>, which accepts 58 * a single argument of the type of event desired;</li> 59 * <li>Mark it with a {@link Subscribe} annotation;</li> 60 * <li>Pass itself to an EventBus instance's {@link #register(Object)} method. 61 * </li> 62 * </ol> 63 * 64 * <h2>Posting Events</h2> 65 * To post an event, simply provide the event object to the 66 * {@link #post(Object)} method. The EventBus instance will determine the type 67 * of event and route it to all registered listeners. 68 * 69 * <p>Events are routed based on their type — an event will be delivered 70 * to any handler for any type to which the event is <em>assignable.</em> This 71 * includes implemented interfaces, all superclasses, and all interfaces 72 * implemented by superclasses. 73 * 74 * <p>When {@code post} is called, all registered handlers for an event are run 75 * in sequence, so handlers should be reasonably quick. If an event may trigger 76 * an extended process (such as a database load), spawn a thread or queue it for 77 * later. (For a convenient way to do this, use an {@link AsyncEventBus}.) 78 * 79 * <h2>Handler Methods</h2> 80 * Event handler methods must accept only one argument: the event. 81 * 82 * <p>Handlers should not, in general, throw. If they do, the EventBus will 83 * catch and log the exception. This is rarely the right solution for error 84 * handling and should not be relied upon; it is intended solely to help find 85 * problems during development. 86 * 87 * <p>The EventBus guarantees that it will not call a handler method from 88 * multiple threads simultaneously, unless the method explicitly allows it by 89 * bearing the {@link AllowConcurrentEvents} annotation. If this annotation is 90 * not present, handler methods need not worry about being reentrant, unless 91 * also called from outside the EventBus. 92 * 93 * <h2>Dead Events</h2> 94 * If an event is posted, but no registered handlers can accept it, it is 95 * considered "dead." To give the system a second chance to handle dead events, 96 * they are wrapped in an instance of {@link DeadEvent} and reposted. 97 * 98 * <p>If a handler for a supertype of all events (such as Object) is registered, 99 * no event will ever be considered dead, and no DeadEvents will be generated. 100 * Accordingly, while DeadEvent extends {@link Object}, a handler registered to 101 * receive any Object will never receive a DeadEvent. 102 * 103 * <p>This class is safe for concurrent use. 104 * 105 * @author Cliff Biffle 106 * @since 10.0 107 */ 108@Beta 109public class EventBus { 110 111 /** 112 * All registered event handlers, indexed by event type. 113 */ 114 private final SetMultimap<Class<?>, EventHandler> handlersByType = 115 Multimaps.newSetMultimap(new ConcurrentHashMap<Class<?>, Collection<EventHandler>>(), 116 new Supplier<Set<EventHandler>>() { 117 @Override 118 public Set<EventHandler> get() { 119 return new CopyOnWriteArraySet<EventHandler>(); 120 } 121 }); 122 123 /** 124 * Logger for event dispatch failures. Named by the fully-qualified name of 125 * this class, followed by the identifier provided at construction. 126 */ 127 private final Logger logger; 128 129 /** 130 * Strategy for finding handler methods in registered objects. Currently, 131 * only the {@link AnnotatedHandlerFinder} is supported, but this is 132 * encapsulated for future expansion. 133 */ 134 private final HandlerFindingStrategy finder = new AnnotatedHandlerFinder(); 135 136 /** queues of events for the current thread to dispatch */ 137 private final ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>> 138 eventsToDispatch = 139 new ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>>() { 140 @Override protected ConcurrentLinkedQueue<EventWithHandler> initialValue() { 141 return new ConcurrentLinkedQueue<EventWithHandler>(); 142 } 143 }; 144 145 /** true if the current thread is currently dispatching an event */ 146 private final ThreadLocal<Boolean> isDispatching = 147 new ThreadLocal<Boolean>() { 148 @Override protected Boolean initialValue() { 149 return false; 150 } 151 }; 152 153 /** 154 * A thread-safe cache for flattenHierarch(). The Class class is immutable. 155 */ 156 private LoadingCache<Class<?>, Set<Class<?>>> flattenHierarchyCache = 157 CacheBuilder.newBuilder() 158 .weakKeys() 159 .build(new CacheLoader<Class<?>, Set<Class<?>>>() { 160 @Override 161 public Set<Class<?>> load(Class<?> concreteClass) throws Exception { 162 List<Class<?>> parents = Lists.newLinkedList(); 163 Set<Class<?>> classes = Sets.newHashSet(); 164 165 parents.add(concreteClass); 166 167 while (!parents.isEmpty()) { 168 Class<?> clazz = parents.remove(0); 169 classes.add(clazz); 170 171 Class<?> parent = clazz.getSuperclass(); 172 if (parent != null) { 173 parents.add(parent); 174 } 175 176 for (Class<?> iface : clazz.getInterfaces()) { 177 parents.add(iface); 178 } 179 } 180 181 return classes; 182 } 183 }); 184 185 /** 186 * Creates a new EventBus named "default". 187 */ 188 public EventBus() { 189 this("default"); 190 } 191 192 /** 193 * Creates a new EventBus with the given {@code identifier}. 194 * 195 * @param identifier a brief name for this bus, for logging purposes. Should 196 * be a valid Java identifier. 197 */ 198 public EventBus(String identifier) { 199 logger = Logger.getLogger(EventBus.class.getName() + "." + identifier); 200 } 201 202 /** 203 * Registers all handler methods on {@code object} to receive events. 204 * Handler methods are selected and classified using this EventBus's 205 * {@link HandlerFindingStrategy}; the default strategy is the 206 * {@link AnnotatedHandlerFinder}. 207 * 208 * @param object object whose handler methods should be registered. 209 */ 210 public void register(Object object) { 211 handlersByType.putAll(finder.findAllHandlers(object)); 212 } 213 214 /** 215 * Unregisters all handler methods on a registered {@code object}. 216 * 217 * @param object object whose handler methods should be unregistered. 218 * @throws IllegalArgumentException if the object was not previously registered. 219 */ 220 public void unregister(Object object) { 221 Multimap<Class<?>, EventHandler> methodsInListener = finder.findAllHandlers(object); 222 for (Entry<Class<?>, Collection<EventHandler>> entry : methodsInListener.asMap().entrySet()) { 223 Set<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey()); 224 Collection<EventHandler> eventMethodsInListener = entry.getValue(); 225 226 if (currentHandlers == null || !currentHandlers.containsAll(entry.getValue())) { 227 throw new IllegalArgumentException( 228 "missing event handler for an annotated method. Is " + object + " registered?"); 229 } 230 currentHandlers.removeAll(eventMethodsInListener); 231 } 232 } 233 234 /** 235 * Posts an event to all registered handlers. This method will return 236 * successfully after the event has been posted to all handlers, and 237 * regardless of any exceptions thrown by handlers. 238 * 239 * <p>If no handlers have been subscribed for {@code event}'s class, and 240 * {@code event} is not already a {@link DeadEvent}, it will be wrapped in a 241 * DeadEvent and reposted. 242 * 243 * @param event event to post. 244 */ 245 public void post(Object event) { 246 Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass()); 247 248 boolean dispatched = false; 249 for (Class<?> eventType : dispatchTypes) { 250 Set<EventHandler> wrappers = getHandlersForEventType(eventType); 251 252 if (wrappers != null && !wrappers.isEmpty()) { 253 dispatched = true; 254 for (EventHandler wrapper : wrappers) { 255 enqueueEvent(event, wrapper); 256 } 257 } 258 } 259 260 if (!dispatched && !(event instanceof DeadEvent)) { 261 post(new DeadEvent(this, event)); 262 } 263 264 dispatchQueuedEvents(); 265 } 266 267 /** 268 * Queue the {@code event} for dispatch during 269 * {@link #dispatchQueuedEvents()}. Events are queued in-order of occurrence 270 * so they can be dispatched in the same order. 271 */ 272 protected void enqueueEvent(Object event, EventHandler handler) { 273 eventsToDispatch.get().offer(new EventWithHandler(event, handler)); 274 } 275 276 /** 277 * Drain the queue of events to be dispatched. As the queue is being drained, 278 * new events may be posted to the end of the queue. 279 */ 280 protected void dispatchQueuedEvents() { 281 // don't dispatch if we're already dispatching, that would allow reentrancy 282 // and out-of-order events. Instead, leave the events to be dispatched 283 // after the in-progress dispatch is complete. 284 if (isDispatching.get()) { 285 return; 286 } 287 288 isDispatching.set(true); 289 try { 290 while (true) { 291 EventWithHandler eventWithHandler = eventsToDispatch.get().poll(); 292 if (eventWithHandler == null) { 293 break; 294 } 295 296 dispatch(eventWithHandler.event, eventWithHandler.handler); 297 } 298 } finally { 299 isDispatching.set(false); 300 } 301 } 302 303 /** 304 * Dispatches {@code event} to the handler in {@code wrapper}. This method 305 * is an appropriate override point for subclasses that wish to make 306 * event delivery asynchronous. 307 * 308 * @param event event to dispatch. 309 * @param wrapper wrapper that will call the handler. 310 */ 311 protected void dispatch(Object event, EventHandler wrapper) { 312 try { 313 wrapper.handleEvent(event); 314 } catch (InvocationTargetException e) { 315 logger.log(Level.SEVERE, 316 "Could not dispatch event: " + event + " to handler " + wrapper, e); 317 } 318 } 319 320 /** 321 * Retrieves a mutable set of the currently registered handlers for 322 * {@code type}. If no handlers are currently registered for {@code type}, 323 * this method may either return {@code null} or an empty set. 324 * 325 * @param type type of handlers to retrieve. 326 * @return currently registered handlers, or {@code null}. 327 */ 328 Set<EventHandler> getHandlersForEventType(Class<?> type) { 329 return handlersByType.get(type); 330 } 331 332 /** 333 * Creates a new Set for insertion into the handler map. This is provided 334 * as an override point for subclasses. The returned set should support 335 * concurrent access. 336 * 337 * @return a new, mutable set for handlers. 338 */ 339 protected Set<EventHandler> newHandlerSet() { 340 return new CopyOnWriteArraySet<EventHandler>(); 341 } 342 343 /** 344 * Flattens a class's type hierarchy into a set of Class objects. The set 345 * will include all superclasses (transitively), and all interfaces 346 * implemented by these superclasses. 347 * 348 * @param concreteClass class whose type hierarchy will be retrieved. 349 * @return {@code clazz}'s complete type hierarchy, flattened and uniqued. 350 */ 351 @VisibleForTesting 352 Set<Class<?>> flattenHierarchy(Class<?> concreteClass) { 353 try { 354 return flattenHierarchyCache.get(concreteClass); 355 } catch (ExecutionException e) { 356 throw Throwables.propagate(e.getCause()); 357 } 358 } 359 360 /** simple struct representing an event and it's handler */ 361 static class EventWithHandler { 362 final Object event; 363 final EventHandler handler; 364 public EventWithHandler(Object event, EventHandler handler) { 365 this.event = event; 366 this.handler = handler; 367 } 368 } 369} 370