@CheckReturnValue @ParametersAreNonnullByDefault
See: Description
Interface | Description |
---|---|
SubscriberExceptionHandler |
Handler for exceptions thrown by event subscribers.
|
Class | Description |
---|---|
AsyncEventBus |
An
EventBus that takes the Executor of your choice and uses it to dispatch events,
allowing dispatch to occur asynchronously. |
DeadEvent |
Wraps an event that was posted, but which had no subscribers and thus could not be delivered.
|
EventBus |
Dispatches events to listeners, and provides ways for listeners to register themselves.
|
SubscriberExceptionContext |
Context for an exception thrown by a subscriber.
|
Annotation Type | Description |
---|---|
AllowConcurrentEvents |
Marks an event subscriber method as being thread-safe.
|
Subscribe |
Marks a method as an event subscriber.
|
See the Guava User Guide article on EventBus
.
Converting an existing EventListener-based system to use the EventBus is easy.
To listen for a specific flavor of event (say, a CustomerChangeEvent)...
Subscribe
annotation.
To register your listener methods with the event producers...
registerCustomerChangeEventListener
method. These methods are rarely defined in common
interfaces, so in addition to knowing every possible producer, you must also know its type.
EventBus.register(Object)
method on an EventBus. You'll need to
make sure that your object shares an EventBus instance with the event producers.
To listen for a common event supertype (such as EventObject or Object)...
To listen for and detect events that were dispatched without listeners...
DeadEvent
. The EventBus will notify you of any events that were
posted but not delivered. (Handy for debugging.)
To keep track of listeners to your events...
To dispatch an event to listeners...
EventBus.post(Object)
method.
The EventBus system and code use the following terms to discuss event distribution:
Subscribe
annotation.
The Event Bus doesn't specify how you use it; there's nothing stopping your application from having separate EventBus instances for each component, or using separate instances to separate events by context or topic. This also makes it trivial to set up and tear down EventBus objects in your tests.
Of course, if you'd like to have a process-wide EventBus singleton, there's nothing stopping you from doing it that way. Simply have your container (such as Guice) create the EventBus as a singleton at global scope (or stash it in a static field, if you're into that sort of thing).
In short, the EventBus is not a singleton because we'd rather not make that decision for you. Use it how you like.
We feel that the Event Bus's @Subscribe
annotation conveys your intentions just as
explicitly as implementing an interface (or perhaps more so), while leaving you free to place
event subscriber methods wherever you wish and give them intention-revealing names.
Traditional Java Events use a listener interface which typically sports only a handful of methods -- typically one. This has a number of disadvantages:
handleChangeEvent
), rather than its
purpose (e.g. recordChangeInJournal
).
The difficulties in implementing this cleanly has given rise to a pattern, particularly common in Swing apps, of using tiny anonymous classes to implement event listener interfaces.
Compare these two cases:
class ChangeRecorder {
void setCustomer(Customer cust) {
cust.addChangeListener(new ChangeListener() {
void customerChanged(ChangeEvent e) {
recordChange(e.getChange());
}
};
}
}
// Class is typically registered by the container.
class EventBusChangeRecorder {
}{@code @Subscribe void recordCustomerChange(ChangeEvent e) {
recordChange(e.getChange());
}
}
The intent is actually clearer in the second case: there's less noise code, and the event subscriber has a clear and meaningful name.
Subscriber<T>
interface?Some have proposed a generic Subscriber<T>
interface for EventBus listeners. This runs
into issues with Java's use of type erasure, not to mention problems in usability.
Let's say the interface looked something like the following:
interface Subscriber<T> {
void handleEvent(T event);
}
Due to erasure, no single class can implement a generic interface more than once with
different type parameters. This is a giant step backwards from traditional Java Events, where
even if actionPerformed
and keyPressed
aren't very meaningful names, at least you
can implement both methods!
Some have freaked out about EventBus's register(Object)
and post(Object)
methods' use of the Object
type.
Object
is used here for a good reason: the Event Bus library places no restrictions on
the types of either your event listeners (as in register(Object)
) or the events
themselves (in post(Object)
).
Event subscriber methods, on the other hand, must explicitly declare their argument type -- the type of event desired (or one of its supertypes). Thus, searching for references to an event class will instantly find all subscriber methods for that event, and renaming the type will affect all subscriber methods within view of your IDE (and any code that creates the event).
It's true that you can rename your @Subscribed
event subscriber methods at will; Event
Bus will not stop this or do anything to propagate the rename because, to Event Bus, the names of
your subscriber methods are irrelevant. Test code that calls the methods directly, of course,
will be affected by your renaming -- but that's what your refactoring tools are for.
register
a listener without any subscriber methods?Nothing at all.
The Event Bus was designed to integrate with containers and module systems, with Guice as the
prototypical example. In these cases, it's convenient to have the container/factory/environment
pass every created object to an EventBus's register(Object)
method.
This way, any object created by the container/factory/environment can hook into the system's event model simply by exposing subscriber methods.
Any problem that can be unambiguously detected by Java's type system. For example, defining a subscriber method for a nonexistent event type.
Immediately upon invoking register(Object)
, the listener being registered is checked
for the well-formedness of its subscriber methods. Specifically, any methods marked with
@Subscribe
must take only a single argument.
Any violations of this rule will cause an IllegalArgumentException
to be thrown.
(This check could be moved to compile-time using APT, a solution we're researching.)
If a component posts events with no registered listeners, it may indicate an error
(typically an indication that you missed a @Subscribe
annotation, or that the listening
component is not loaded).
(Note that this is not necessarily indicative of a problem. There are many cases where an application will deliberately ignore a posted event, particularly if the event is coming from code you don't control.)
To handle such events, register a subscriber method for the DeadEvent
class. Whenever
EventBus receives an event with no registered subscribers, it will turn it into a DeadEvent
and pass it your way -- allowing you to log it or otherwise recover.
Because subscriber methods on your listener classes are normal methods, you can simply call them from your test code to simulate the EventBus.
Copyright © 2010–2020. All rights reserved.