Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

EventBusExplained

cpovirk edited this pageSep 25, 2023 ·12 revisions

EventBus

EventBus allows publish-subscribe-style communication between componentswithout requiring the components to explicitly register with one another (andthus be aware of each other). It is designed exclusively to replace traditionalJava in-process event distribution using explicit registration. It isnot ageneral-purpose publish-subscribe system, nor is it intended for interprocesscommunication.

AvoidEventBus

We recommend against usingEventBus. It was designed many years ago, andnewer libraries offer better ways to decouple components and react to events.

To decouple components, we recommend a dependency-injection framework. ForAndroid code, most apps useDagger. For server code,common options includeGuiceandSpring. Frameworks typically offer a way to register multiple listenersindependently and then request them together as a set(Dagger,Guice,Spring).

To react to events, we recommend a reactive-streams framework likeRxJava (supplemented with itsRxAndroid extension if you arebuilding for Android) orProject Reactor. (For thebasics of translating code from using an event bus to using a reactive-streamsframework, see these two guides:1,2.) Someusages of EventBus may be better written usingKotlin coroutines,includingFlow andChannels. Yet other usages arebetter served by individual libraries that provide specialized support forparticular use cases.

Disadvantages of EventBus include:

  • It makes the cross-references between producer and subscriber harder tofind. This can complicate debugging, lead to unintentional reentrant calls,and force apps to eagerly initialize all possible subscribers at startuptime.
  • It uses reflection in ways that break when code is processed byoptimizers/minimizers likeR8 and Proguard.
  • It doesn't offer a way to wait for multiple events before taking action. Forexample, it doesn't offer a way to wait for multiple producers to all reportthat they're "ready," nor does it offer a way to batch multiple events froma single producer together.
  • It doesn't support backpressure and other features needed for resilience.
  • It doesn't provide much control of threading.
  • It doesn't offer much monitoring.
  • It doesn't propagate exceptions, so apps don't have a way to react to them.
  • It doesn't interoperate well with RxJava, coroutines, and other morecommonly used alternatives.
  • It imposes requirements on the lifecycle of its subscribers. For example, ifan event occurs between when one subscriber is removed and the nextsubscriber is added, the event is dropped.
  • Its performance is suboptimal, especially under Android.
  • Itdoesn't support parameterized types.
  • With the introduction of lambdas in Java 8,EventBus went from lessverbose than listeners tomore verbose.

Example

// Class is typically registered by the container.classEventBusChangeRecorder {@SubscribepublicvoidrecordCustomerChange(ChangeEvente) {recordChange(e.getChange());  }}// somewhere during initializationeventBus.register(newEventBusChangeRecorder());// much laterpublicvoidchangeCustomer()ChangeEventevent =getChangeEvent();eventBus.post(event);}

One-Minute Guide

Converting an existingEventListener-based system to use theEventBus iseasy.

For Listeners

To listen for a specific flavor of event (say, aCustomerChangeEvent)...

  • ...in traditional Java events: implement an interface defined with theevent -- such asCustomerChangeEventListener.
  • ...withEventBus: create a method that acceptsCustomerChangeEventas its sole argument, and mark it with the@Subscribeannotation.

To register your listener methods with the event producers...

  • ...in traditional Java events: pass your object to each producer'sregisterCustomerChangeEventListener method. These methods are rarelydefined in common interfaces, so in addition to knowing every possibleproducer, you must also know its type.
  • ...withEventBus: pass your object to theEventBus.register(Object)method on anEventBus. You'll need to make sure that your object shares anEventBus instance with the event producers.

To listen for a common event supertype (such asEventObject orObject)...

  • ...in traditional Java events: not easy.
  • ...withEventBus: events are automatically dispatched to listeners ofany supertype, allowing listeners for interface types or "wildcardlisteners" forObject.

To listen for and detect events that were dispatched without listeners...

  • ...in traditional Java events: add code to each event-dispatching method(perhaps using AOP).
  • ...withEventBus: subscribe toDeadEvent.TheEventBus will notify you of any events that were posted but notdelivered. (Handy for debugging.)

For Producers

To keep track of listeners to your events...

  • ...in traditional Java events: write code to manage a list of listenersto your object, including synchronization, or use a utility class likeEventListenerList.
  • ...withEventBus:EventBus does this for you.

To dispatch an event to listeners...

  • ...in traditional Java events: write a method to dispatch events to eachevent listener, including error isolation and (if desired) asynchronicity.
  • ...withEventBus: pass the event object to anEventBus'sEventBus.post(Object)method.

Glossary

TheEventBus system and code use the following terms to discuss eventdistribution:

EventAny object that may beposted to a bus.
SubscribingThe act of registering alistener with anEventBus, so that itssubscriber methods will receive events.
ListenerAn object that wishes to receive events, by exposingsubscriber methods.
Subscriber methodA public method that theEventBus should use to deliverposted events. Subscriber methods are marked by the@Subscribe annotation.
Posting an eventMaking the event available to anylisteners through theEventBus.

FAQ

Why must I create my own Event Bus, rather than using a singleton?

EventBus doesn't specify how you use it; there's nothing stopping yourapplication from having separateEventBus instances for each component, orusing separate instances to separate events by context or topic. This also makesit trivial to set up and tear downEventBus objects in your tests.

Of course, if you'd like to have a process-wideEventBus singleton, there'snothing stopping you from doing it that way. Simply have your container (such asGuice) create theEventBus as a singleton at global scope (or stash it in astatic field, if you're into that sort of thing).

In short,EventBus is not a singleton because we'd rather not make thatdecision for you. Use it how you like.

Can I unregister a listener from the Event Bus?

Yes, usingEventBus.unregister, but we find this is needed only rarely:

  • Most listeners are registered on startup or lazy initialization, and persistfor the life of the application.
  • Scope-specificEventBus instances can handle temporary event distribution(e.g. distributing events among request-scoped objects)
  • For testing,EventBus instances can be easily created and thrown away,removing the need for explicit unregistration.

Why use an annotation to mark subscriber methods, rather than requiring the listener to implement an interface?

We feel that the Event Bus's@Subscribe annotation conveys your intentionsjust as explicitly as implementing an interface (or perhaps more so), whileleaving you free to place event subscriber methods wherever you wish and givethem intention-revealing names.

Traditional Java Events use a listener interface which typically sports only ahandful of methods -- typically one. This has a number of disadvantages:

  • Any one class can only implement a single response to a given event.
  • Listener interface methods may conflict.
  • The method must be named after the event (e.g.handleChangeEvent), ratherthan its purpose (e.g.recordChangeInJournal).
  • Each event usually has its own interface, without a common parent interfacefor a family of events (e.g. all UI events).

The difficulties in implementing this cleanly has given rise to a pattern,particularly common in Swing apps, of using tiny anonymous classes to implementevent listener interfaces.

Compare these two cases:

classChangeRecorder {voidsetCustomer(Customercust) {cust.addChangeListener(newChangeListener() {publicvoidcustomerChanged(ChangeEvente) {recordChange(e.getChange());      }    };  }}

versus

// Class is typically registered by the container.classEventBusChangeRecorder {@SubscribepublicvoidrecordCustomerChange(ChangeEvente) {recordChange(e.getChange());  }}

The intent is actually clearer in the second case: there's less noise code, andthe event subscriber has a clear and meaningful name.

What about a genericSubscriber<T> interface?

Some have proposed a genericSubscriber<T> interface forEventBus listeners.This runs into issues with Java's use of type erasure, not to mention problemsin usability.

Let's say the interface looked something like the following:

interfaceSubscriber<T> {voidhandleEvent(Tevent);}

Due to erasure, no single class can implement a generic interface more thanonce with different type parameters. This is a giant step backwards fromtraditional Java Events, where even ifactionPerformed andkeyPressedaren't very meaningful names, at least you can implement both methods!

Doesn'tEventBus destroy static typing and eliminate automated refactoring support?

Some have freaked out aboutEventBus'sregister(Object) andpost(Object)methods' use of theObject type.

Object is used here for a good reason: the Event Bus library places norestrictions on the types of either your event listeners (as inregister(Object)) or the events themselves (inpost(Object)).

Event subscriber methods, on the other hand, must explicitly declare theirargument type -- the type of event desired (or one of its supertypes). Thus,searching for references to an event class will instantly find all subscribermethods for that event, and renaming the type will affect all subscriber methodswithin view of your IDE (and any code that creates the event).

It's true that you can rename your@Subscribed event subscriber methods atwill; Event Bus will not stop this or do anything to propagate the renamebecause, to Event Bus, the names of your subscriber methods are irrelevant. Testcode that calls the methods directly, of course, will be affected by yourrenaming -- but that's what your refactoring tools are for. We see this as afeature, not a bug: being able to rename your subscriber methods at will letsyou make their meaning clearer.

What happens if Iregister a listener without any subscriber methods?

Nothing at all.

The Event Bus was designed to integrate with containers and module systems, withGuice as the prototypical example. In these cases, it's convenient to have thecontainer/factory/environment passevery created object to anEventBus'sregister(Object) method.

This way, any object created by the container/factory/environment can hook intothe system's event model simply by exposing subscriber methods.

What Event Bus problems can be detected at compile time?

Any problem that can be unambiguously detected by Java's type system. Forexample, defining a subscriber method for a nonexistent event type.

What Event Bus problems can be detected immediately at registration?

Immediately upon invokingregister(Object) , the listener being registered ischecked for thewell-formedness of its subscriber methods. Specifically, anymethods marked with@Subscribe must take only a single argument.

Any violations of this rule will cause anIllegalArgumentException to bethrown.

(This check could be moved to compile-time using APT, a solution we'reresearching.)

WhatEventBus problems may only be detected later, at runtime?

If a component posts events with no registered listeners, itmay indicate anerror (typically an indication that you missed a@Subscribe annotation, orthat the listening component is not loaded).

(Note that this isnot necessarily indicative of a problem. There are manycases where an application will deliberately ignore a posted event, particularlyif the event is coming from code you don't control.)

To handle such events, register a subscriber method for theDeadEvent class.WheneverEventBus receives an event with no registered subscribers, it willturn it into aDeadEvent and pass it your way -- allowing you to log it orotherwise recover.

How do I test event listeners and their subscriber methods?

Because subscriber methods on your listener classes are normal methods, you cansimply call them from your test code to simulate theEventBus.

Why can't I do <magic thing> withEventBus?

EventBus is designed to deal with a large class of use cases really, reallywell. We prefer hitting the nail on the head for most use cases to doingdecently on all use cases.

Additionally, makingEventBus extensible -- and making it useful andproductive to extend, whilestill allowing ourselves to make additions to thecoreEventBus API that don't conflict with any of your extensions -- is anextremely difficult problem.

If you really, really need magic thing X, thatEventBus can't currentlyprovide, you should design your own alternative.

Clone this wiki locally

[8]ページ先頭

©2009-2025 Movatter.jp