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 * 042 * <ol> 043 * <li>Expose a public method, known as the <i>event subscriber</i>, which accepts a single 044 * argument of the type of event desired; 045 * <li>Mark it with a {@link Subscribe} annotation; 046 * <li>Pass itself to an EventBus instance's {@link #register(Object)} method. 047 * </ol> 048 * 049 * <h2>Posting Events</h2> 050 * 051 * <p>To post an event, simply provide the event object to the {@link #post(Object)} method. The 052 * EventBus instance will determine the type of event and route it to all registered listeners. 053 * 054 * <p>Events are routed based on their type — an event will be delivered to any subscriber for 055 * any type to which the event is <em>assignable.</em> This includes implemented interfaces, all 056 * superclasses, and all interfaces implemented by superclasses. 057 * 058 * <p>When {@code post} is called, all registered subscribers for an event are run in sequence, so 059 * subscribers should be reasonably quick. If an event may trigger an extended process (such as a 060 * database load), spawn a thread or queue it for later. (For a convenient way to do this, use an 061 * {@link AsyncEventBus}.) 062 * 063 * <h2>Subscriber Methods</h2> 064 * 065 * <p>Event subscriber methods must accept only one argument: the event. 066 * 067 * <p>Subscribers should not, in general, throw. If they do, the EventBus will catch and log the 068 * exception. This is rarely the right solution for error handling and should not be relied upon; it 069 * is intended solely to help find problems during development. 070 * 071 * <p>The EventBus guarantees that it will not call a subscriber method from multiple threads 072 * simultaneously, unless the method explicitly allows it by bearing the {@link 073 * AllowConcurrentEvents} annotation. If this annotation is not present, subscriber methods need not 074 * worry about being reentrant, unless also called from outside the EventBus. 075 * 076 * <h2>Dead Events</h2> 077 * 078 * <p>If an event is posted, but no registered subscribers can accept it, it is considered "dead." 079 * To give the system a second chance to handle dead events, they are wrapped in an instance of 080 * {@link DeadEvent} and reposted. 081 * 082 * <p>If a subscriber for a supertype of all events (such as Object) is registered, no event will 083 * ever be considered dead, and no DeadEvents will be generated. Accordingly, while DeadEvent 084 * extends {@link Object}, a subscriber registered to receive any Object will never receive a 085 * DeadEvent. 086 * 087 * <p>This class is safe for concurrent use. 088 * 089 * <p>See the Guava User Guide article on <a 090 * href="https://github.com/google/guava/wiki/EventBusExplained">{@code EventBus}</a>. 091 * 092 * @author Cliff Biffle 093 * @since 10.0 094 */ 095@Beta 096public class EventBus { 097 098 private static final Logger logger = Logger.getLogger(EventBus.class.getName()); 099 100 private final String identifier; 101 private final Executor executor; 102 private final SubscriberExceptionHandler exceptionHandler; 103 104 private final SubscriberRegistry subscribers = new SubscriberRegistry(this); 105 private final Dispatcher dispatcher; 106 107 /** Creates a new EventBus named "default". */ 108 public EventBus() { 109 this("default"); 110 } 111 112 /** 113 * Creates a new EventBus with the given {@code identifier}. 114 * 115 * @param identifier a brief name for this bus, for logging purposes. Should be a valid Java 116 * identifier. 117 */ 118 public EventBus(String identifier) { 119 this( 120 identifier, 121 MoreExecutors.directExecutor(), 122 Dispatcher.perThreadDispatchQueue(), 123 LoggingHandler.INSTANCE); 124 } 125 126 /** 127 * Creates a new EventBus with the given {@link SubscriberExceptionHandler}. 128 * 129 * @param exceptionHandler Handler for subscriber exceptions. 130 * @since 16.0 131 */ 132 public EventBus(SubscriberExceptionHandler exceptionHandler) { 133 this( 134 "default", 135 MoreExecutors.directExecutor(), 136 Dispatcher.perThreadDispatchQueue(), 137 exceptionHandler); 138 } 139 140 EventBus( 141 String identifier, 142 Executor executor, 143 Dispatcher dispatcher, 144 SubscriberExceptionHandler exceptionHandler) { 145 this.identifier = checkNotNull(identifier); 146 this.executor = checkNotNull(executor); 147 this.dispatcher = checkNotNull(dispatcher); 148 this.exceptionHandler = checkNotNull(exceptionHandler); 149 } 150 151 /** 152 * Returns the identifier for this event bus. 153 * 154 * @since 19.0 155 */ 156 public final String identifier() { 157 return identifier; 158 } 159 160 /** Returns the default executor this event bus uses for dispatching events to subscribers. */ 161 final Executor executor() { 162 return executor; 163 } 164 165 /** Handles the given exception thrown by a subscriber with the given context. */ 166 void handleSubscriberException(Throwable e, SubscriberExceptionContext context) { 167 checkNotNull(e); 168 checkNotNull(context); 169 try { 170 exceptionHandler.handleException(e, context); 171 } catch (Throwable e2) { 172 // if the handler threw an exception... well, just log it 173 logger.log( 174 Level.SEVERE, 175 String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, e), 176 e2); 177 } 178 } 179 180 /** 181 * Registers all subscriber methods on {@code object} to receive events. 182 * 183 * @param object object whose subscriber methods should be registered. 184 */ 185 public void register(Object object) { 186 subscribers.register(object); 187 } 188 189 /** 190 * Unregisters all subscriber methods on a registered {@code object}. 191 * 192 * @param object object whose subscriber methods should be unregistered. 193 * @throws IllegalArgumentException if the object was not previously registered. 194 */ 195 public void unregister(Object object) { 196 subscribers.unregister(object); 197 } 198 199 /** 200 * Posts an event to all registered subscribers. This method will return successfully after the 201 * event has been posted to all subscribers, and regardless of any exceptions thrown by 202 * subscribers. 203 * 204 * <p>If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not 205 * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted. 206 * 207 * @param event event to post. 208 */ 209 public void post(Object event) { 210 Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event); 211 if (eventSubscribers.hasNext()) { 212 dispatcher.dispatch(event, eventSubscribers); 213 } else if (!(event instanceof DeadEvent)) { 214 // the event had no subscribers and was not itself a DeadEvent 215 post(new DeadEvent(this, event)); 216 } 217 } 218 219 @Override 220 public String toString() { 221 return MoreObjects.toStringHelper(this).addValue(identifier).toString(); 222 } 223 224 /** Simple logging handler for subscriber exceptions. */ 225 static final class LoggingHandler implements SubscriberExceptionHandler { 226 static final LoggingHandler INSTANCE = new LoggingHandler(); 227 228 @Override 229 public void handleException(Throwable exception, SubscriberExceptionContext context) { 230 Logger logger = logger(context); 231 if (logger.isLoggable(Level.SEVERE)) { 232 logger.log(Level.SEVERE, message(context), exception); 233 } 234 } 235 236 private static Logger logger(SubscriberExceptionContext context) { 237 return Logger.getLogger(EventBus.class.getName() + "." + context.getEventBus().identifier()); 238 } 239 240 private static String message(SubscriberExceptionContext context) { 241 Method method = context.getSubscriberMethod(); 242 return "Exception thrown by subscriber method " 243 + method.getName() 244 + '(' 245 + method.getParameterTypes()[0].getName() 246 + ')' 247 + " on subscriber " 248 + context.getSubscriber() 249 + " when dispatching event: " 250 + context.getEvent(); 251 } 252 } 253}