001/* 002 * Copyright (C) 2007 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.eventbus; 016 017import static com.google.common.base.Preconditions.checkNotNull; 018 019import com.google.common.annotations.Beta; 020import com.google.common.base.MoreObjects; 021import com.google.common.util.concurrent.MoreExecutors; 022import java.lang.reflect.Method; 023import java.util.Iterator; 024import java.util.Locale; 025import java.util.concurrent.Executor; 026import java.util.logging.Level; 027import java.util.logging.Logger; 028 029/** 030 * Dispatches events to listeners, and provides ways for listeners to register themselves. 031 * 032 * <p>The EventBus allows publish-subscribe-style communication between components without requiring 033 * the components to explicitly register with one another (and thus be aware of each other). It is 034 * designed exclusively to replace traditional Java in-process event distribution using explicit 035 * registration. It is <em>not</em> a general-purpose publish-subscribe system, nor is it intended 036 * for interprocess communication. 037 * 038 * <h2>Receiving Events</h2> 039 * 040 * <p>To receive events, an object should: 041 * <ol> 042 * <li>Expose a public method, known as the <i>event subscriber</i>, which accepts a single argument 043 * of the type of event desired; 044 * <li>Mark it with a {@link Subscribe} annotation; 045 * <li>Pass itself to an EventBus instance's {@link #register(Object)} method. 046 * </ol> 047 * 048 * <h2>Posting Events</h2> 049 * 050 * <p>To post an event, simply provide the event object to the {@link #post(Object)} method. The 051 * EventBus instance will determine the type of event and route it to all registered listeners. 052 * 053 * <p>Events are routed based on their type — an event will be delivered to any subscriber for 054 * any type to which the event is <em>assignable.</em> This includes implemented interfaces, all 055 * superclasses, and all interfaces implemented by superclasses. 056 * 057 * <p>When {@code post} is called, all registered subscribers for an event are run in sequence, so 058 * subscribers should be reasonably quick. If an event may trigger an extended process (such as a 059 * database load), spawn a thread or queue it for later. (For a convenient way to do this, use an 060 * {@link AsyncEventBus}.) 061 * 062 * <h2>Subscriber Methods</h2> 063 * 064 * <p>Event subscriber methods must accept only one argument: the event. 065 * 066 * <p>Subscribers should not, in general, throw. If they do, the EventBus will catch and log the 067 * exception. This is rarely the right solution for error handling and should not be relied upon; it 068 * is intended solely to help find problems during development. 069 * 070 * <p>The EventBus guarantees that it will not call a subscriber method from multiple threads 071 * simultaneously, unless the method explicitly allows it by bearing the 072 * {@link AllowConcurrentEvents} annotation. If this annotation is not present, subscriber methods 073 * need not worry about being reentrant, unless also called from outside the EventBus. 074 * 075 * <h2>Dead Events</h2> 076 * 077 * <p>If an event is posted, but no registered subscribers can accept it, it is considered "dead." 078 * To give the system a second chance to handle dead events, they are wrapped in an instance of 079 * {@link DeadEvent} and reposted. 080 * 081 * <p>If a subscriber for a supertype of all events (such as Object) is registered, no event will 082 * ever be considered dead, and no DeadEvents will be generated. Accordingly, while DeadEvent 083 * extends {@link Object}, a subscriber registered to receive any Object will never receive a 084 * DeadEvent. 085 * 086 * <p>This class is safe for concurrent use. 087 * 088 * <p>See the Guava User Guide article on 089 * <a href="https://github.com/google/guava/wiki/EventBusExplained">{@code EventBus}</a>. 090 * 091 * @author Cliff Biffle 092 * @since 10.0 093 */ 094@Beta 095public class EventBus { 096 097 private static final Logger logger = Logger.getLogger(EventBus.class.getName()); 098 099 private final String identifier; 100 private final Executor executor; 101 private final SubscriberExceptionHandler exceptionHandler; 102 103 private final SubscriberRegistry subscribers = new SubscriberRegistry(this); 104 private final Dispatcher dispatcher; 105 106 /** 107 * Creates a new EventBus named "default". 108 */ 109 public EventBus() { 110 this("default"); 111 } 112 113 /** 114 * Creates a new EventBus with the given {@code identifier}. 115 * 116 * @param identifier a brief name for this bus, for logging purposes. Should be a valid Java 117 * identifier. 118 */ 119 public EventBus(String identifier) { 120 this( 121 identifier, 122 MoreExecutors.directExecutor(), 123 Dispatcher.perThreadDispatchQueue(), 124 LoggingHandler.INSTANCE); 125 } 126 127 /** 128 * Creates a new EventBus with the given {@link SubscriberExceptionHandler}. 129 * 130 * @param exceptionHandler Handler for subscriber exceptions. 131 * @since 16.0 132 */ 133 public EventBus(SubscriberExceptionHandler exceptionHandler) { 134 this( 135 "default", 136 MoreExecutors.directExecutor(), 137 Dispatcher.perThreadDispatchQueue(), 138 exceptionHandler); 139 } 140 141 EventBus( 142 String identifier, 143 Executor executor, 144 Dispatcher dispatcher, 145 SubscriberExceptionHandler exceptionHandler) { 146 this.identifier = checkNotNull(identifier); 147 this.executor = checkNotNull(executor); 148 this.dispatcher = checkNotNull(dispatcher); 149 this.exceptionHandler = checkNotNull(exceptionHandler); 150 } 151 152 /** 153 * Returns the identifier for this event bus. 154 * 155 * @since 19.0 156 */ 157 public final String identifier() { 158 return identifier; 159 } 160 161 /** 162 * Returns the default executor this event bus uses for dispatching events to subscribers. 163 */ 164 final Executor executor() { 165 return executor; 166 } 167 168 /** 169 * Handles the given exception thrown by a subscriber with the given context. 170 */ 171 void handleSubscriberException(Throwable e, SubscriberExceptionContext context) { 172 checkNotNull(e); 173 checkNotNull(context); 174 try { 175 exceptionHandler.handleException(e, context); 176 } catch (Throwable e2) { 177 // if the handler threw an exception... well, just log it 178 logger.log( 179 Level.SEVERE, 180 String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, e), 181 e2); 182 } 183 } 184 185 /** 186 * Registers all subscriber methods on {@code object} to receive events. 187 * 188 * @param object object whose subscriber methods should be registered. 189 */ 190 public void register(Object object) { 191 subscribers.register(object); 192 } 193 194 /** 195 * Unregisters all subscriber methods on a registered {@code object}. 196 * 197 * @param object object whose subscriber methods should be unregistered. 198 * @throws IllegalArgumentException if the object was not previously registered. 199 */ 200 public void unregister(Object object) { 201 subscribers.unregister(object); 202 } 203 204 /** 205 * Posts an event to all registered subscribers. This method will return successfully after the 206 * event has been posted to all subscribers, and regardless of any exceptions thrown by 207 * subscribers. 208 * 209 * <p>If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not 210 * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted. 211 * 212 * @param event event to post. 213 */ 214 public void post(Object event) { 215 Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event); 216 if (eventSubscribers.hasNext()) { 217 dispatcher.dispatch(event, eventSubscribers); 218 } else if (!(event instanceof DeadEvent)) { 219 // the event had no subscribers and was not itself a DeadEvent 220 post(new DeadEvent(this, event)); 221 } 222 } 223 224 @Override 225 public String toString() { 226 return MoreObjects.toStringHelper(this).addValue(identifier).toString(); 227 } 228 229 /** 230 * Simple logging handler for subscriber exceptions. 231 */ 232 static final class LoggingHandler implements SubscriberExceptionHandler { 233 static final LoggingHandler INSTANCE = new LoggingHandler(); 234 235 @Override 236 public void handleException(Throwable exception, SubscriberExceptionContext context) { 237 Logger logger = logger(context); 238 if (logger.isLoggable(Level.SEVERE)) { 239 logger.log(Level.SEVERE, message(context), exception); 240 } 241 } 242 243 private static Logger logger(SubscriberExceptionContext context) { 244 return Logger.getLogger(EventBus.class.getName() + "." + context.getEventBus().identifier()); 245 } 246 247 private static String message(SubscriberExceptionContext context) { 248 Method method = context.getSubscriberMethod(); 249 return "Exception thrown by subscriber method " 250 + method.getName() 251 + '(' 252 + method.getParameterTypes()[0].getName() 253 + ')' 254 + " on subscriber " 255 + context.getSubscriber() 256 + " when dispatching event: " 257 + context.getEvent(); 258 } 259 } 260}