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