Einführung in den EventBus
Was ist EventBus?
- Die Idee vom
EventBushttps://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.
EventBusvereinfacht 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
EventSubscriberist generisch deklariert, so dass es beiEventSubscriber<Object> somitonEvent( 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 MethodeEventBus.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.
- 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 value") ); EventBus.publish( new Holder ( 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.Typeanmelden und Senden. Der Quellcode ist ein wenig komplex:
EventBus.subscribe( new TypeReference(){}.getType(), new EventSubscriber() {
@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
Stringextra 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, beisubscribe()ist statt einEventSubscriberein 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 istsubscribe(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 derEventBusden Programmcode im AWT EventThreadabarbeitet. - 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
EventBussetzt den Programmcode standardmäßig in den EDT, kann ihn aber auch von einem anderenThreadabarbeiten lassen. - System.out.println( EventBus.getGlobalEventService() ); // org.bushe.swing.event.SwingEventService@173a10f
- Standardmäßig nutzt
EventBusalso 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.)
- 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
EventBuskann 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“
Threadbearbeitet haben möchte, schreibt man wie üblichEventBus.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 denEventSubscriberin 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 beimEventBusabgemeldet. - Ist das
System.gc()raus, steht immer noch „Hallo“, weil das Objekt noch da ist. - Soll
EventBuskeinen WeakReference-Behälter um den Listener bauen, so nutzt mansubscribeStrongly(XXX, EventSubscriber)bzw.subscribeExactlyStrongly(XXX, EventSubscriber).
Was fehlt noch?
EventBuskann die Listener aufzählenEventBuskann 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
4 Kommentare
someDude ·
Erstes Beispiel = bad smell "Subscriptions are weak by default. \[...\] This anonymous inner class subscriber will likely get garbage collected almost immediately." http://eventbus.org/pivot.html#WarningSubscriptionsAreWeak
bennidi ·
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
bennidi ·
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.
Gedärme: Event Bus für Google Guice ·
\[...\] ist, steht das für einen Event Bus noch aus. Über eine Lösung habe ich schon geschrieben: http://www.tutego.de/blog/javainsel/2009/03/einfuhrung-in-den-eventbus/. Ein Event Bus sollte natürlich mit einem DI-Container harmonieren. Nutzer von Google Guice \[...\]
Die bisherigen Kommentare wurden aus WordPress übernommen. Neue Kommentare sind derzeit geschlossen.