Tento článok je z veľkej časti prebratý (preložený) zo stránok Tomasza Dziurka.
Spoločnosť Google predstavila v rámci projektu Guava (vo verzii 10) nový balíček eventbus
s niekoľkými zaujímavými triedami, ktoré sa zaoberajú návrhovým vzorom listener (resp. published – subscriber). V tomto článku si predstavíme triedu EventBus
a iné s ňou súvisiace veci.
Do projektu je možné vložiť podporu napr. pomocou Maven závislosti:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>21.0</version> </dependency>
Základy
Na to, aby sme mohli zachytávať nejaké udalosti (events) potrebujeme listener triedu. Na vytvorenie takejto triedy v štýle google-guava nemusíme implementovať žiadne rozhranie ani dediť z nejakej triedy. Môže to byť ľubovoľná trieda, ktorá bude obsahovať jeden požadovaný element – metódu označenú anotáciou @Subscribe
:
public class MyEventListener { public int lastMessage = 0; @Subscribe public void listen(MyEvent event) { lastMessage = event.getMessage(); } public int getLastMessage() { return lastMessage; } }
Atribút lastMessage
je použiťý nižšie na overenie, či bola udalosť úspešne zachytená.
A samozrejme budeme potrebovať triedu s udalosťou, ktorú budeme posielať:
public class MyEvent { private final int message; public MyEvent(int message) { this.message = message; } public int getMessage() { return message; } }
Ako to funguje
Najlepší spôsob ako ukázať nejakú akciu, je napísať niekoľko testov, takže sa poďme pozrieť, aké jednoduché je použiť EventBus
:
@Test public void shouldReceiveEvent() throws Exception { // given EventBus eventBus = new EventBus("test"); EventListener listener = new MyEventListener(); eventBus.register(listener); // when eventBus.post(new MyEvent(200)); // then assertTrue(listener.getLastMessage() == 200); }
Tento test nemôže byť jednoduchší, vytvoríme inštanciu EventBus
a inštanciu listener triedy, potom zaregistrujeme listener a odošleme novú udalosť (event).
MultiListener
Guava umožňuje vytvoriť listener, ktorý reaguje na viacero rozličných udalostí. Jedine čo musíme urobiť, je dať anotáciu @Subscribe
viacerým metódam:
public class MultipleListener { public Integer lastInteger; public Long lastLong; @Subscribe public void listenInteger(Integer event) { lastInteger = event; } @Subscribe public void listenLong(Long event) { lastLong = event; } public Integer getLastInteger() { return lastInteger; } public Long getLastLong() { return lastLong; } }
Jednoduchý príklad:
@Test public void shouldReceiveMultipleEvents() throws Exception { // given EventBus eventBus = new EventBus("test"); MultipleListener multiListener = new MultipleListener(); eventBus.register(multiListener); // when eventBus.post(new Integer(100)); eventBus.post(new Long(800)); // then assertTrue(multiListener.getLastInteger() == 100); assertTrue(multiListener.getLastLong() == 800L); }
Nemusíme implementovať viacero rozhraní, Guava poskytuje krásne a čisté riešenie len vďaka jednej anotácii.
Podobne môžeme vytvoriť aj viacero listener tried, ktoré reagujú na rovnakú udalosť. Toto je zvlášť vhodné, ak napríklad nejaká zmena v aplikácii vedie k prekresleniu a aktualizácii dát viacerých komponentov v GUI.
Niečo navyše
Poďme sa pozrieť na viac zaujímavých vlastností triedy EventBus
.
Dead Event
Prvá nezvyčajná vec je trieda DeadEvent
, preddefinovaná udalosť, ktorá je vyvolaná keď sa odošle ľubovoľný typ udalosti, na ktorý nikto nereaguje. Aby sme videle, ako to funguje, vytvorme listener, ktorý zachytáva udalosť DeadEvent
:
/** * Listener waiting for the event that any message was posted but not delivered to anyone */ public class DeadEventListener { boolean notDelivered = false; @Subscribe public void listen(DeadEvent event) { notDelivered = true; } public boolean isNotDelivered() { return notDelivered; } }
V teste môžeme vidieť, ako to funguje:
@Test public void shouldDetectEventWithoutListeners() throws Exception { // given EventBus eventBus = new EventBus("test"); DeadEventListener deadEventListener = new DeadEventListener(); eventBus.register(deadEventListener); // when eventBus.post(new OurTestEvent(200)); assertTrue(deadEventListener.isNotDelivered()); }
Takže ak žiadny listener nečakal na odoslanú udalosť, EventBus
vyvolal DeadEvent
, takže môžeme nejako reagovať, minimálne zalogovať upozornenie, že k takejto situácii prišlo.
Hierarchia udalostí
Ďalšou zaujímavou vecou je, že listener triedy dokážu využiť existujúcu hierarchiu tried udalostí. Takže ak listener A
čaká na udalost A
a udalosť A
má potomka B
, potom listener zareaguje na oba typy udalostí – A
aj B
. Toto si môžeme ukázať na krátkom príklade. Vytvorme si dve listener triedy, jeden pre udalosť typu Number
a druhý pre udalosť typu Integer
(ktorý ak neviete je podtypom triedy Number
):
public class NumberListener { private Number lastMessage; @Subscribe public void listen(Number integer) { lastMessage = integer; } public Number getLastMessage() { return lastMessage; } }
public class IntegerListener { private Integer lastMessage; @Subscribe public void listen(Integer integer) { lastMessage = integer; } public Integer getLastMessage() { return lastMessage; } }
Funkčnosť môžeme vidieť na jednoduchom teste:
@Test public void shouldGetEventsFromSubclass() throws Exception { // given EventBus eventBus = new EventBus("test"); IntegerListener integerListener = new IntegerListener(); NumberListener numberListener = new NumberListener(); eventBus.register(integerListener); eventBus.register(numberListener); // when eventBus.post(new Integer(100)); // then assertTrue(integerListener.getLastMessage() == 100); assertTrue(numberListener.getLastMessage().intValue() == 100); //when eventBus.post(new Long(200L)); // then // this one should has the old value as it listens only for Integers assertTrue(integerListener.getLastMessage() == 100); assertTrue(numberListener.getLastMessage().longValue() == 200L); }
V tejto metóde vidíme, že prvá udalosť (new Integer(100)
) je spracovaná oboma listener triedami, ale druhá (new Long(200L)
) len jednou.
Táto vlastnosť sa môže využiť na vytvorenie všeobecnejšej listener triedy, ktorá reaguje na väčší rozsah udalostí a podrobnejšie listener triedy môžu slúžiť pre špecifické účely.
Prílohy
Maven projekt s vyššie uvedeným príkladmi: