Google Guava EventBus

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:

Zdroje

Leave a Reply

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *