Uvod u MB ambasador

1. Pregled

Jednostavno rečeno, MB Ambassador je sabirnica događaja visokih performansi koja koristi semantiku objaviti-pretplatiti.

Poruke se emitiraju jednom ili više vršnjaka bez prethodnog znanja o tome koliko ima pretplatnika ili kako koriste poruku.

2. Ovisnost Mavena

Prije nego što počnemo koristiti knjižnicu, moramo dodati ovisnost mb поміdera:

 net.engio mbador 1.3.1 

3. Osnovno rukovanje događajima

3.1. Jednostavan primjer

Počet ćemo s jednostavnim primjerom objavljivanja poruke:

privatni dispečer MB ambasadora = novi MB ambasador (); private String messageString; @Prije javne void pripremiteTests () {depecher.subscribe (this); } @Test javna void kadaStringDispatched_thenHandleString () {depecher.post ("TestString"). Now (); assertNotNull (messageString); assertEquals ("TestString", messageString); } @Handler public void handleString (String message) {messageString = message; } 

Na vrhu ove testne klase vidimo stvaranje a MB ambasador sa svojim zadanim konstruktorom. Dalje, u @Prije metodu, nazivamo pretplatite se () i proslijedite referencu na sam razred.

U pretplatite se (), dispečer pregledava pretplatnika za @ Rukovalac bilješke.

I, u prvom testu, zovemo dispečer.post (…) .sada () poslati poruku - što rezultira handleString () biti pozvan.

Ovaj početni test pokazuje nekoliko važnih koncepata. Bilo koji Objekt može biti pretplatnik, pod uvjetom da ima jednu ili više metoda označenih s @ Rukovalac. Pretplatnik može imati bilo koji broj rukovatelja.

Koristimo testne objekte koji se pretplaćuju na sebe radi jednostavnosti, ali u većini proizvodnih scenarija distributeri poruka ući će u različite klase od potrošača.

Metode rukovatelja imaju samo jedan ulazni parametar - poruku i ne mogu izbaciti provjerene iznimke.

Slično kao pretplatite se () metoda, metoda pošta prihvaća bilo koju Objekt. Ovaj Objekt isporučuje se pretplatnicima.

Kada je poruka objavljena, ona se isporučuje svim slušateljima koji su se pretplatili na vrstu poruke.

Dodajmo još jedan rukovatelj porukama i pošaljite drugu vrstu poruke:

private Integer messageInteger; @Test public void whenIntegerDispatched_thenHandleInteger () {depecher.post (42) .now (); assertNull (messageString); assertNotNull (messageInteger); assertTrue (42 == messageInteger); } @Handler javna void handleInteger (Integer message) {messageInteger = message; } 

Očekivano, kad otpremimoan Cijeli broj, handleInteger () naziva se i handleString () nije. Jedan dispečer može se koristiti za slanje više vrsta poruka.

3.2. Mrtve poruke

Pa kamo ide poruka kad za nju ne postoji rukovatelj? Dodajmo novi rukovatelj događajima, a zatim pošaljite treću vrstu poruke:

privatni objekt deadEvent; @Test public void whenLongDispatched_thenDeadEvent () {depecher.post (42L) .now (); assertNull (messageString); assertNull (messageInteger); assertNotNull (deadEvent); assertTrue (deadEvent instanceof Long); assertTrue (42L == (Long) deadEvent); } @Handler javna void handleDeadEvent (poruka DeadMessage) {deadEvent = message.getMessage (); } 

U ovom testu šaljemo a Dugo umjesto Cijeli broj. Ni handleInteger () ni handleString () se zovu, ali handleDeadEvent () je.

Kada nema rukovatelja za poruku, ona se zamota u DeadMessage objekt. Budući da smo dodali rukovatelj za Deadmessage, hvatamo ga.

DeadMessage može se sigurno zanemariti; ako aplikacija ne treba pratiti mrtve poruke, može im se dopustiti da nikamo ne idu.

4. Korištenje hijerarhije događaja

Slanje Niz i Cijeli broj događaji su ograničavajući. Stvorimo nekoliko klasa poruka:

javna klasa Message {} javna klasa AckMessage proširuje Message {} javna klasa RejectMessage proširuje Message {int kod; // postavljači i dobivači}

Imamo jednostavnu osnovnu klasu i dvije klase koje je proširuju.

4.1. Slanje osnovne klase Poruka

Počet ćemo s Poruka događaji:

privatni dispečer MB ambasadora = novi MB ambasador (); privatna poruka poruke; privatni AckMessage ackMessage; private RejectMessage rejectMessage; @Prije javne void pripremiteTests () {depecher.subscribe (this); } @Test public void whenMessageDispatched_thenMessageHandled () {depecher.post (nova poruka ()). Now (); assertNotNull (poruka); assertNull (ackMessage); assertNull (rejectMessage); } @Handler public void handleMessage (poruka poruke) {this.message = message; } @Handler javna void handleRejectMessage (poruka RejectMessage) {rejectMessage = message; } @Handler javna void handleAckMessage (AckMessage poruka) {ackMessage = message; }

Otkrijte MB Ambassador - autobus pub-sub događaja visokih performansi. To nas ograničava na upotrebu Poruke ali dodaje dodatni sloj sigurnosti tipa.

Kad pošaljemo a Poruka, handleMessage () prima ga. Druga dva rukovatelja nemaju.

4.2. Slanje poruke podrazreda

Pošaljite a Odbaci poruku:

@Test public void whenRejectDispatched_thenMessageAndRejectHandled () {depecher.post (new RejectMessage ()). Now (); assertNotNull (poruka); assertNotNull (rejectMessage); assertNull (ackMessage); }

Kad pošaljemo a Odbaci poruku oba handleRejectMessage () i handleMessage () primiti.

Od Odbaci poruku proteže se Poruka, the Poruka voditelj je primio, uz RejectMessage rukovatelj.

Provjerimo ovo ponašanje pomoću AckMessage:

@Test public void whenAckDispatched_thenMessageAndAckHandled () {depecher.post (new AckMessage ()). Now (); assertNotNull (poruka); assertNotNull (ackMessage); assertNull (rejectMessage); }

Baš kao što smo i očekivali, kad pošaljemo AckMessage, oboje handleAckMessage () i handleMessage () primiti.

5. Filtriranje poruka

Organiziranje poruka po vrstama već je moćna značajka, ali možemo ih još više filtrirati.

5.1. Filtriraj prema klasi i podrazredu

Kad smo postavili Odbaci poruku ili AckMessage, događaj smo primili i u rukovatelju događajima za određenu vrstu i u osnovnoj klasi.

Ovaj problem hijerarhije tipa možemo riješiti izradom Poruka sažetak i stvaranje klase kao što je GenericMessage. Ali što ako nemamo ovaj luksuz?

Možemo koristiti filtre za poruke:

privatna poruka baseMessage; podporuka privatne poruke; @Test javna void whenMessageDispatched_thenMessageFiltered () {depecher.post (nova poruka ()). Now (); assertNotNull (baseMessage); assertNull (subMessage); } @Test public void whenRejectDispatched_thenRejectFiltered () {depecher.post (new RejectMessage ()). Now (); assertNotNull (subMessage); assertNull (baseMessage); } @Handler (filtri = {@Filter (Filters.RejectSubtypes.class)}) public void handleBaseMessage (Message message) {this.baseMessage = message; } @Handler (filtri = {@Filter (Filters.SubtypesOnly.class)}) public void handleSubMessage (Poruka poruke) {this.subMessage = message; }

The filtri parametar za @ Rukovalac bilješka prihvaća a Razred koji provodi IMessageFilter. Knjižnica nudi dva primjera:

The Filteri.RejectSubtypes čini kako mu samo ime govori: filtrirat će sve podvrste. U ovom slučaju to vidimo Odbaci poruku ne obrađuje handleBaseMessage ().

The Filteri.Samo podvrste također čini kako mu samo ime govori: filtrirat će sve osnovne vrste. U ovom slučaju to vidimo Poruka ne obrađuje handleSubMessage ().

5.2. IMessageFilter

The Filteri.RejectSubtypes i Filteri.Samo podvrste oboje provesti IMessageFilter.

RejectSubTypes uspoređuje klasu poruke s definiranim vrstama poruka i dopustit će samo poruke koje su jednake jednoj od njezinih vrsta, za razliku od bilo koje potklase.

5.3. Filtriraj s uvjetima

Srećom, postoji lakši način filtriranja poruka. MB Ambassador podržava podskup izraza Java EL kao uvjete za filtriranje poruka.

Filtrirajmo a Niz poruka temeljena na njezinoj duljini:

private String testString; @Test javna void kadaLongStringDispatched_thenStringFiltered () {depecher.post ("foobar!"). Now (); assertNull (testString); } @Handler (condition = "msg.length () <7") javna void handleStringMessage (String poruka) {this.testString = message; }

"Foobar!" poruka ima sedam znakova i filtrira se. Pošaljite kraću Niz:

 @Test javna void kadaShortStringDispatched_thenStringHandled () {depecher.post ("foobar"). Now (); assertNotNull (testString); }

Sada je "foobar" dugačak samo šest znakova i prošao je kroz njega.

Naše Odbaci poruku sadrži polje s pristupnikom. Napišimo filtar za to:

private RejectMessage rejectMessage; @Test public void whenWrongRejectDispatched_thenRejectFiltered () {RejectMessage testReject = novo RejectMessage (); testReject.setCode (-1); dispečer.post (testReject) .now (); assertNull (rejectMessage); assertNotNull (subMessage); assertEquals (-1, ((RejectMessage) subMessage) .getCode ()); } @Handler (uvjet = "msg.getCode ()! = -1") javna void handleRejectMessage (RejectMessage rejectMessage) {this.rejectMessage = rejectMessage; }

I ovdje možemo tražiti metodu na objektu i filtrirati poruku ili ne.

5.4. Snimite filtrirane poruke

Slično DeadEvents, možda bismo željeli zabilježiti i obraditi filtrirane poruke. Postoji namjenski mehanizam za bilježenje filtriranih događaja. Filtrirani događaji tretiraju se drugačije od "mrtvih" događaja.

Napišimo test koji to ilustrira:

private String testString; privatno FilteredMessage filteredMessage; private DeadMessage deadMessage; @Test javna void kadaLongStringDispatched_thenStringFiltered () {depecher.post ("foobar!"). Now (); assertNull (testString); assertNotNull (filteredMessage); assertTrue (filteredMessage.getMessage () instanceof String); assertNull (deadMessage); } @Handler (condition = "msg.length () <7") javna void handleStringMessage (String poruka) {this.testString = message; } @Handler public void handleFilterMessage (FilteredMessage message) {this.filteredMessage = message; } @Handler javna praznina handleDeadMessage (DeadMessage deadMessage) {this.deadMessage = deadMessage; } 

Uz dodatak a FilteredMessage voditelju, možemo pratiti Žice koji su filtrirani zbog svoje duljine. The filterMessage sadrži naše predugo Niz dok deadMessage ostaci null.

6. Asinkrono slanje i rukovanje porukama

Do sada su se svi naši primjeri koristili sinkrono slanje poruka; kad smo nazvali post.now () poruke su isporučene svakom obrađivaču u istoj niti koju smo pozvali objaviti () iz.

6.1. Asinkrono slanje

The MB Ambassador.post () vraća SyncAsyncPostCommand. Ova klasa nudi nekoliko metoda, uključujući:

  • sada() - sinhrono slati poruke; poziv će se blokirati dok sve poruke ne budu isporučene
  • asinkrono () - izvršava objavu poruke asinkrono

Upotrijebimo asinkronu otpremu u klasi uzorka. Koristit ćemo Awaitility u ovim testovima za pojednostavljenje koda:

privatni dispečer MB ambasadora = novi MB ambasador (); private String testString; privatni AtomicBoolean spreman = novi AtomicBoolean (netočno); @Test javna void whenAsyncDispatched_thenMessageReceived () {depecher.post ("foobar"). Asinkrono (); await (). tillAtomic (spreman, jednakTo (istina)); assertNotNull (testString); } @Handler public void handleStringMessage (String message) {this.testString = message; ready.set (true); }

Mi zovemo asinkrono () u ovom testu i upotrijebite AtomicBoolean kao zastava sa čekati() da pričekate da nit isporuke isporuči poruku.

Ako prokomentiramo poziv čekati(), riskiramo da test ne padne, jer provjeravamo testString prije završetka niti isporuke.

6.2. Pozivanje asinkronog rukovatelja

Asinkrono slanje omogućuje davatelju poruka da se vrati na obradu poruka prije nego što se poruke dostave svakom obrađivaču, ali svejedno poziva svakog obrađivača redom, a svaki obrađivač mora pričekati da prethodni završi.

To može dovesti do problema ako jedan voditelj izvede skupu operaciju.

MB Ambassador pruža mehanizam za asinkroni poziv upravljača. Obrađivači konfigurirani za ovo primaju poruke u svoju nit:

privatni Integer testInteger; private String invocationThreadName; privatni AtomicBoolean spreman = novi AtomicBoolean (netočno); @Test public void whenHandlerAsync_thenHandled () {depecher.post (42) .now (); await (). tillAtomic (spreman, jednakTo (istina)); assertNotNull (testInteger); assertFalse (Thread.currentThread (). getName (). jednako (invocationThreadName)); } @Handler (delivery = Invoke.Asynchronously) javna void handleIntegerMessage (Integer message) {this.invocationThreadName = Thread.currentThread (). GetName (); this.testInteger = poruka; ready.set (true); }

Rukovoditelji mogu zatražiti asinkroni poziv s dostava = Prizvati. Asinkrono nekretnina na Rukovatelj bilješka. To provjeravamo u našem testu uspoređujući Nit imena u metodi otpreme i voditelj.

7. Prilagođavanje MB ambasadora

Do sada smo koristili instancu MB ambasadora sa zadanom konfiguracijom. Ponašanje dispečera može se modificirati bilješkama, slično onima koje smo do sada vidjeli; pokrivat ćemo još nekoliko za završetak ovog vodiča.

7.1. Rukovanje iznimkama

Rukovatelji ne mogu definirati provjerene iznimke. Umjesto toga, dispečeru se može dostaviti IPublicationErrorHandler kao argument svom konstruktoru:

javna klasa MB AmbassadorConfigurationTest provodi IPublicationErrorHandler {privatni dispečer MB ambasadora; private String messageString; private Throwable errorCause; @Prije javne void pripremiteTests () {dispečer = novi MB ambasador (ovaj); dispečer.subscribe (ovo); } @Test public void whenErrorOccurs_thenErrorHandler () {depecher.post ("Pogreška"). Now (); assertNull (messageString); assertNotNull (errorCause); } @Test public void whenNoErrorOccurs_thenStringHandler () {depecher.post ("Pogreška"). Now (); assertNull (Uzrok pogreške); assertNotNull (messageString); } @Handler public void handleString (String message) {if ("Error" .equals (message)) {throw new Error ("BOOM"); } messageString = poruka; } @Override public void handleError (pogreška PublicationError) {errorCause = error.getCause (). GetCause (); }}

Kada handleString () baca an Pogreška, sprema se u errorCause.

7.2. Prioritet rukovatelja

Rukovatelji pozivaju se obrnutim redoslijedom kako su dodani, ali to nije ponašanje na koje se želimo osloniti. Čak i uz mogućnost pozivanja rukovatelja u njihovim nitima, možda ćemo i dalje morati znati kojim će redoslijedom biti pozvani.

Prioritet rukovatelja možemo postaviti izričito:

privatni popis LinkedList = novi LinkedList (); @Test javna void whenRejectDispatched_thenPriorityHandled () {depecher.post (new RejectMessage ()). Now (); // Stavke bi trebale iskočiti () obrnutim redoslijedom prioriteta assertTrue (1 == list.pop ()); assertTrue (3 == list.pop ()); assertTrue (5 == list.pop ()); } @Handler (prioritet = 5) public void handleRejectMessage5 (RejectMessage rejectMessage) {list.push (5); } @Handler (prioritet = 3) public void handleRejectMessage3 (RejectMessage rejectMessage) {list.push (3); } @Handler (prioritet = 2, rejectSubtypes = true) javna void handleMessage (Message rejectMessage) logger.error ("Reject handler # 3"); list.push (3); } @Handler (prioritet = 0) public void handleRejectMessage0 (RejectMessage rejectMessage) {list.push (1); } 

Rukovatelji se pozivaju od najvišeg prioriteta do najnižeg. Rukovatelji sa zadanim prioritetom, koji je nula, nazivaju se zadnjim. Vidimo da se rukovatelj brojevima pop () isključeno obrnutim redoslijedom.

7.3. Odbaci podvrste, jednostavan način

što se dogodilo handleMessage () u gore navedenom testu? Ne moramo koristiti RejectSubTypes.class za filtriranje naših podtipova.

RejectSubTypes je logička zastava koja pruža isto filtriranje kao i klasa, ali s boljim performansama od IMessageFilter provedba.

Ipak, i dalje moramo koristiti implementaciju na temelju filtra samo za prihvaćanje podtipova.

8. Zaključak

MB Ambassador je jednostavna i jednostavna knjižnica za prosljeđivanje poruka između objekata. Poruke se mogu organizirati na razne načine i mogu se slati sinkrono ili asinkrono.

I kao i uvijek, primjer je dostupan u ovom GitHub projektu.