Einführung in den EventBus

Was ist EventBus?

  • Die Idee vom EventBus https://eventbus.dev.java.net/ ist schnell in einem Satz beschrieben:
    • Biete einen Publish/Subscribe-Ereignisdienst für Applikationen an, die innerhalb einer JVM laufen.
    • Anders als also JMS, funktioniert EventBus nur in einer JVM, aber nicht über Rechnergrenzen.
  • Ereignisbehandlung wird üblicherweise über Observer/Observable oder über Listener realisiert.
    • Listener sind aber lästig zu schreiben: Man benötigt XXXEventListener Schnittstellen und Implementierungen, addXXXListener(), removeXXXListener() und fireEventListener() Methoden und vielleicht XXXEvent-Klassen.
  • EventBus vereinfacht das und mit zwei Typen und drei Methoden ist ein erstes Beispiel programmiert.

Erstes Beispiel


import org.bushe.swing.event.EventBus;
import org.bushe.swing.event.EventSubscriber;

class Observer
{
Observer()
{
EventBus.subscribe( Object.class, new EventSubscriber<Object>() {
@Override public void onEvent( Object evt ) {
System.out.println( evt );
}
} );
}
}

public class First
{
public static void main( String args[] )
{
new Observer();
EventBus.publish( "Hallo Welt" );
}
}

Analyse

  • EventBus.publish( Object ) sendet ein Ereignis an alle Interessenten aus.
  • EventBus.subscribe( Class, EventSubscriber ) meldet für einen speziellen Klassentyp einen Listener an.
    • Das besondere: Es ist hierarchisch. Wir senden mit EventBus.publish( „Hallo“ ) einen String, aber da String eine Unterklasse von Object ist, bekommt unser „Alles“-Listener die Nachricht.
    • EventBus.unsubscribe() meldet den Interessenten wieder ab.
  • Der EventSubscriber ist generisch deklariert, so dass es bei EventSubscriber<Object> somit onEvent( Object evt ) heißt.

Hierarchien

  • Mit der Möglichkeit Event-Hierarchien zu bilden gibt es eine große Flexibilität.
  • So horcht folgendes auf alle IOException-Events: EventBus.subscribe( IOException.class, new EventSubscriber() { … EventBus.publish( new FileNotFoundException() );
  • Ist dieses Matchen nicht gewünscht, verwendet man statt EventBus.subscribe() die Methode EventBus.subscribeExactly().

Hierarchien bei Generischen Typen (1)

  • Bei Generics funktioniert ein .class nicht wie erwartet.
    • Es liefert System.out.println( new Holder<String>().getClass() ); und System.out.println( new Holder<StringBuffer>().getClass() ); immer nur class javax.xml.ws.Holder.
  • Was passiert bei EventBus.subscribe( Holder.class, new EventSubscriber() { … und EventBus.publish( new Holder<String>(„String value“) ); EventBus.publish( new Holder<Date>( new Date() ) );
  • In beiden Fällen wird der Listener benachrichtigt.

Hierarchien bei Generischen Typen (2)

  • Soll eine Trennung aufgrund des generischen Typs stattfinden, lässt sich auf ein java.lang.reflect.Type anmelden und Senden. Der Quellcode ist ein wenig komplex:


EventBus.subscribe( new TypeReference<Holder<Date>>(){}.getType(), new EventSubscriber<Object>() {
@Override public void onEvent( Object evt ) {
System.out.println( ((Holder)evt).value );
}
} );


EventBus.publish( new TypeReference<Holder<String>>(){}.getType(), new Holder<String>("String value") );
EventBus.publish( new TypeReference<Holder<Date>>(){}.getType(), new Holder<Date>( new Date() ) );

Jetzt wird nur noch das Datum empfangen und auf den Bildschirm kommt:

Thu Mar 26 14:28:15 CET 2009

Topics

  • Im Regelfall sind nicht alle Interessenten an allen Ereignissen interessiert.
  • Man kann nun verschiedene Ereignistypen definieren, doch wenn man etwa Strings verschicken möchte, ist es lästig, diesen String extra in in Ereignisobjekt zu verpacken.
  • Die Lösung sind Topics, also bestimmte Themen, zu denen man sich anmelden kann und zu denen man schicken kann.
  • Es ändern sich in der API zwei Dinge: Bei publish() ist der Topic anzugeben, bei subscribe() ist statt ein EventSubscriber ein EventTopicSubscriber nötig, da der Listener neben dem Event auch den Topic übergibt.

Beispiel mit Topics


EventBus.subscribe( "Error", new EventTopicSubscriber() {
@Override public void onEvent( String topic, Object evt ) { System.out.printf( "'%s' für Topic '%s'%n", evt, topic ); }
} );

EventBus.publish( "Error", "Hallo Welt" );

Liefert dann

‚Hallo Welt‘ für Topic ‚Error‘

Für mehrere Topics anmelden

  • Soll ein EventTopicSubscriber für mehrere Topics angemeldet werden, so kann man natürlich schreiben: EventTopicSubscriber ets = … EventBus.subscribe( topic1, ets ); EventBus.subscribe( topic2, ets );
  • Eine weitere subscribe()-Methode ist subscribe(java.util.regex.Pattern, EventTopicSubscriber).
    • Damit lassen sich leicht über reguläre Ausdrücke Gruppen bilden.

EventBus.subscribe( Pattern.compile( „Error|Warning“ ), new EventTopicSubscriber() { …

EventBus.publish( „Error“, „Hallo Welt“ );
EventBus.publish( „Info“, „Hallo Welt“ ); // Kommt nicht an
EventBus.publish( „Warning“, „Hallo Welt“ );

EventBus und AWT Event Dispatching Thread (EDT)

  • Setzt man in die onEvent()-Methode die Anweisung System.out.println( Thread.currentThread() ); // Thread[AWT-EventQueue-0,6,main] so findet man, dass der EventBus den Programmcode im AWT Event Thread abarbeitet.
  • Das ist natürlich gut für Aktionen, die an den Swing-Komponenten vorgenommen werden.
    • Zum Beispiel: Ein beliebiger Thread lädt Daten und möchte nach dem Laden eine Statuszeile aktualisieren. Schickt der Thread dann mit publish() ein Ereignis, wird der Programmcode vom Empfänger im EDT ausgeführt, sodass dort etwa ein setText() auf einem JLabel der Statuszeile erlaubt ist.
  • Auf der anderen Seite hat das zur Konsequenz, dass der Programmcode schnell sein muss, damit der EDT nicht zu lange blockiert wird.
  • Bei nicht-AWT-Anwendungen ist die Abarbeitung im EDT unsinnig

EventBus und SwingEventService

  • Der EventBus setzt den Programmcode standardmäßig in den EDT, kann ihn aber auch von einem anderen Thread abarbeiten lassen.
  • System.out.println( EventBus.getGlobalEventService() ); // org.bushe.swing.event.SwingEventService@173a10f
  • Standardmäßig nutzt EventBus also intern eine SwingEventService-Objekt.
  • Alternativ kann man schreiben: SwingEventService eventing = new SwingEventService(); eventing.subscribe( Object.class, new EventSubscriber() { @Override public void onEvent( Object evt ) { System.out.println( evt ); } } ); eventing.publish( „Hallo“ );

SwingEventService und ThreadSafeEventService

  • Neben dem SwingEventService gibt es den ThreadSafeEventService für eine Abarbeitung, die nicht im EDT stattfindet.
    • Beide Implementieren die Schnittstelle EventService. (Genaugenommen ist SwingEventService eine Unterklasse von ThreadSafeEventService.)
  • Man schreibt dann: EventService eventing = new ThreadSafeEventService(); eventing.subsribe(…); eventing.publish(…);

EventServiceLocator (1)

  • Den EventBus kann man nun so umstellen, dass er standardmäßig den ThreadSafeEventService nutzt.
    • Dazu wird intern ein EventServiceLocator eingesetzt.

    System.out.println( EventBus.getGlobalEventService() ); // org.bushe.swing.event.SwingEventService System.out.println( EventServiceLocator.getEventBusService() ); // org.bushe.swing.event.SwingEventService System.out.println( EventServiceLocator.getSwingEventService() ); // org.bushe.swing.event.SwingEventService

  • Der EventServiceLocator verwaltet also zwei unterschiedliche Event-Services.

EventServiceLocator (2)

try { EventServiceLocator.setEventService(EventServiceLocator.SERVICE_NAME_EVENT_BUS, new ThreadSafeEventService()); } catch ( EventServiceExistsException e ) { e.printStackTrace(); }

System.out.println( EventBus.getGlobalEventService() ); // org.bushe.swing.event.ThreadSafeEventService System.out.println( EventServiceLocator.getEventBusService() ); // org.bushe.swing.event.ThreadSafeEventService System.out.println( EventServiceLocator.getSwingEventService() ); // org.bushe.swing.event.SwingEventService

Achtung: Das muss an den Anfang bevor ein Event je den Bus sieht!

EventServiceLocator (3)

  • Wenn man nun Ereignisse über einen „normalen“ Thread bearbeitet haben möchte, schreibt man wie üblich EventBus.publish()/subscribe().
  • Sollen die Eventanweisungen in den EDT, schreibt man EventServiceLocator.getSwingEventService().publish()/subscribe().

Hängende Referenzen bei Listenern

  • Ein häufiges Problem bei Listenern insbesondere bei Swing-Anwendungen sind angemeldete Listener, für die der Interessent aber schon weg ist. Ein Szenario:
    • Ein Textfeld einer Maske meldet eine Listener an, um bei Modelländerungen die Daten darstellen zu können.
    • Das Model speichert den Listener und indirekt auch eine Referenz auf das Textfeld.
    • Die Maske verschwindet, somit auch der Interessent für Modelländerungen.
    • Da aber das Model den Listener und indirekt das Textfeld referenziert, kann der GC das Textfeld gar nicht freigeben.
    • Daher müssen Listener immer abgemeldet werden, oder…?

EventBus und WeakReference (1)

  • Damit Listener bei nicht mehr aktiven Horchern automatisch verschwinden, kann man WeakReferences einsetzen.
  • Eine WeakReference ist wie ein Proxy, der ein anderes Objekt ummantelt. Ist die WeakReference die einzige Referenz, die sich für das Objekt interessiert, so kann sie beim nächsten GC das Objekt wegräumen. Der Proxy wird dann ebenfalls nicht mehr benutzt.
  • Standardmäßig mantelt die EventBus.subscribe(XXX, EventSubscriber)-Methoden den EventSubscriber in eine WeakReference.
    • Wenn es also keinen starken Verweis auf den EventSubscriber mehr von außen gibt, wird der EventBus diesen Listener automatisch abmelden.

EventBus und WeakReference (2)


public class First
{
First()
{
new Observer();
}

public static void main( String args[] )
{
new First();
// System.gc();
EventBus.publish( "Hallo Welt" );
}
}

EventBus und WeakReference (3)

  • Der Konstruktor erzeugt einen Observer, der aber nicht referenziert wird. Nach dem der Konstruktor abgearbeitet wurde, ist das Exemplar Freiwild für den GC.
  • Wenn in main() System.gc() aufgerufen wird, wird aufgeräumt. Damit werden die WeakReferences entsort also auch der Listener beim EventBus abgemeldet.
  • Ist das System.gc() raus, steht immer noch „Hallo“, weil das Objekt noch da ist.
  • Soll EventBus keinen WeakReference-Behälter um den Listener bauen, so nutzt man subscribeStrongly(XXX, EventSubscriber) bzw. subscribeExactlyStrongly(XXX, EventSubscriber).

Was fehlt noch?

  • EventBus kann die Listener aufzählen
  • EventBus kann Veto
  • Listener können auch über Annotationen angemeldet werden
  • Timer können Events überwachen
  • Events können gechached werden, sodass spätere Anmelder die Events auch bekommen

Seminar-Werbung. Diese Weiterbildungen sind neu bei tutego

Ähnliche Beiträge

4 Gedanken zu “Einführung in den EventBus

  1. Danke für den Artikel und die Erklärungen. Der vorgestellte EventBus hat aber mehrere Schwächen. Zunächst sind viele Methoden statisch und es ist deshalb nicht möglich mehrere Instanzen zu verwenden. Außerdem muss man sich an die onEvent Konvention halten oder den AnnotationProcessor verwenden. Im Performance-Vergleich liegt EventBus außerdem an letzter Stelle, wie man hier sehen kann: codeblock.engio.net/?p=37‎ (Benchmark-Code auf github). Bessere Frameworks sind MBassador und/oder Google Guavas EventBus. Note: Ich bin Autor von MBassador.

  2. Update: Mit Mbassador existiert das Problem der Speicherlecks nicht mehr, da per Default WeakReferences benutzt werden. Somit kann man alles in den Bus reinstopfen und muss sich nicht um Deregistrierung kümmern. Das ist sehr praktisch wenn die Objekte von anderen Frameworks wie Swing, Spring, Guice etc. gemanaged werden

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert