Introduktion til MBassador

1. Oversigt

Kort sagt, MBassador er en højtydende begivenhedsbus, der anvender semantikken for publish-subscribe.

Beskeder udsendes til en eller flere jævnaldrende uden forudgående viden om, hvor mange abonnenter der er, eller hvordan de bruger beskeden.

2. Maven-afhængighed

Inden vi kan bruge biblioteket, skal vi tilføje ambassadørafhængighed:

 net.engio-ambassadør 1.3.1 

3. Grundlæggende håndtering af begivenheder

3.1. Simpelt eksempel

Vi starter med et simpelt eksempel på offentliggørelse af en besked:

privat MBassador afsender = ny MBassador (); privat String beskedString; @Før offentlige ugyldige prepareTests () {dispatcher.subscribe (dette); } @Test offentlig ugyldig nårStringDispatched_thenHandleString () {dispatcher.post ("TestString"). Nu (); assertNotNull (messageString); assertEquals ("TestString", messageString); } @Handler offentlig ugyldig handleString (streng besked) {messageString = besked; } 

Øverst i denne testklasse ser vi oprettelsen af ​​en MBassador med sin standardkonstruktør. Dernæst i @Før metode, kalder vi abonner () og videregive en henvisning til selve klassen.

I abonner (), afsenderen inspicerer abonnenten for @Handler kommentarer.

Og i den første test kalder vi dispatcher.post (…) .now () at sende meddelelsen - hvilket resulterer i handleString () bliver kaldt.

Denne indledende test demonstrerer flere vigtige begreber. Nogen Objekt kan være en abonnent, så længe den har en eller flere metoder, der er kommenteret @Handler. En abonnent kan have et hvilket som helst antal handlere.

Vi bruger testobjekter, der abonnerer på sig selv for enkelhedens skyld, men i de fleste produktionsscenarier vil meddelelsessendere i forskellige klasser end forbrugerne.

Handler-metoder har kun én inputparameter - meddelelsen og kan ikke kaste nogen kontrollerede undtagelser.

Svarende til abonner () metode accepterer postmetoden enhver Objekt. Det her Objekt leveres til abonnenter.

Når en meddelelse sendes, leveres den til alle lyttere, der har abonneret på meddelelsestypen.

Lad os tilføje en anden meddelelsesbehandler og sende en anden meddelelsestype:

privat Heltalsbesked Heltals; @Test offentlig ugyldig nårIntegerDispatched_thenHandleInteger () {dispatcher.post (42) .now (); assertNull (messageString); assertNotNull (messageInteger); assertTrue (42 == messageInteger); } @Handler offentligt ugyldigt handleInteger (Heltalsmeddelelse) {messageInteger = meddelelse; } 

Som forventet, når vi senderen Heltal, handleInteger () kaldes, og handleString () er ikke. En enkelt afsender kan bruges til at sende mere end én meddelelsestype.

3.2. Døde beskeder

Så hvor går en besked hen, når der ikke er nogen handler til den? Lad os tilføje en ny begivenhedshåndterer og derefter sende en tredje meddelelsestype:

privat objekt deadEvent; @Test offentlig ugyldig nårLongDispatched_thenDeadEvent () {dispatcher.post (42L) .now (); assertNull (messageString); assertNull (messageInteger); assertNotNull (deadEvent); assertTrue (deadEvent-forekomst af Long); assertTrue (42L == (Long) deadEvent); } @Handler offentlig ugyldig handleDeadEvent (DeadMessage-meddelelse) {deadEvent = message.getMessage (); } 

I denne test sender vi en Lang i stedet for en Heltal. Ingen af ​​dem handleInteger () heller ikke handleString () kaldes, men handleDeadEvent () er.

Når der ikke er nogen håndterere til en besked, bliver den pakket ind i en DeadMessage objekt. Da vi tilføjede en handler til Deadmessage, vi fanger det.

DeadMessage kan ignoreres sikkert hvis en applikation ikke har brug for at spore døde meddelelser, kan de få lov til at gå nogen steder.

4. Brug af et begivenhedshierarki

Afsendelse Snor og Heltal begivenheder er begrænsende. Lad os oprette et par beskedklasser:

public class Message {} public class AckMessage extends Message {} public class RejectMessage extends Message {int code; // settere og getters}

Vi har en simpel basisklasse og to klasser, der udvider den.

4.1. Afsendelse af en basisklasse Besked

Vi starter med Besked begivenheder:

privat MBassador afsender = ny MBassador (); privat besked besked; private AckMessage ackMessage; private RejectMessage rejectMessage; @Før offentlige ugyldige prepareTests () {dispatcher.subscribe (dette); } @Test offentlig ugyldig nårMessageDispatched_thenMessageHandled () {dispatcher.post (ny besked ()). Nu (); assertNotNull (besked); assertNull (ackMessage); assertNull (afvis besked); } @Handler offentligt ugyldigt handleMessage (Beskedbesked) {denne besked = besked; } @ Handler offentlig ugyldig handleRejectMessage (RejectMessage-meddelelse) {rejectMessage = meddelelse; } @Handler offentlig ugyldig handleAckMessage (AckMessage-meddelelse) {ackMessage = meddelelse; }

Oplev MBassador - en højtydende pub-sub-begivenhedsbus. Dette begrænser os til at bruge Beskeder men tilføjer et ekstra lag af typesikkerhed.

Når vi sender en Besked, handleMessage () modtager det. De to andre håndterere gør det ikke.

4.2. Afsendelse af en underklassemeddelelse

Lad os sende en Afvis besked:

@Test offentligt ugyldigt, nårRejectDispatched_thenMessageAndRejectHandled () {dispatcher.post (ny RejectMessage ()). Nu (); assertNotNull (besked); assertNotNull (afvis besked); assertNull (ackMessage); }

Når vi sender en Afvis besked begge handleRejectMessage () og handleMessage () modtage det.

Siden Afvis besked strækker sig Besked, det Besked handler modtog det, ud over RejectMessage handler.

Lad os kontrollere denne adfærd med en AckMessage:

@Test offentlig ugyldig nårAckDispatched_thenMessageAndAckHandled () {dispatcher.post (ny AckMessage ()). Nu (); assertNotNull (besked); assertNotNull (ackMessage); assertNull (afvis besked); }

Ligesom vi forventede, når vi sender en AckMessage, begge handleAckMessage () og handleMessage () modtage det.

5. Filtrering af beskeder

Organisering af meddelelser efter type er allerede en stærk funktion, men vi kan filtrere dem endnu mere.

5.1. Filtrer på klasse og underklasse

Da vi sendte en Afvis besked eller AckMessage, vi modtog begivenheden i både begivenhedshåndtereren for den bestemte type og i basisklassen.

Vi kan løse dette type hierarkiproblem ved at lave Besked abstrakt og skabe en klasse som GenericMessage. Men hvad hvis vi ikke har denne luksus?

Vi kan bruge beskedfiltre:

privat besked baseMessage; privat besked subMessage; @Test offentligt ugyldigt nårMessageDispatched_thenMessageFiltered () {dispatcher.post (ny besked ()). Nu (); assertNotNull (baseMessage); assertNull (undermeddelelse); } @Test offentlig ugyldig nårRejectDispatched_thenRejectFiltered () {dispatcher.post (ny RejectMessage ()). Nu (); assertNotNull (subMessage); assertNull (baseMessage); } @Handler (filtre = {@Filter (Filters.RejectSubtypes.class)}) offentlig ugyldig handleBaseMessage (Beskedbesked) {this.baseMessage = besked; } @Handler (filtre = {@Filter (Filters.SubtypesOnly.class)}) offentlig ugyldig handleSubMessage (Beskedbesked) {this.subMessage = besked; }

Det filtre parameter for @Handler annotering accepterer a Klasse der implementerer IMessageFilter. Biblioteket tilbyder to eksempler:

Det Filters.RejectSubtypes gør som navnet antyder: det filtrerer alle undertyper ud. I dette tilfælde ser vi det Afvis besked håndteres ikke af handleBaseMessage ().

Det Filtre. Undertyper Kun gør også som navnet antyder: det filtrerer alle basetyper ud. I dette tilfælde ser vi det Besked håndteres ikke af handleSubMessage ().

5.2. IMessageFilter

Det Filters.RejectSubtypes og Filtre. Undertyper Kun begge implementerer IMessageFilter.

RejectSubTypes sammenligner klassen af ​​meddelelsen med dens definerede meddelelsestyper og tillader kun gennem meddelelser, der svarer til en af ​​dens typer, i modsætning til eventuelle underklasser.

5.3. Filtrer med betingelser

Heldigvis er der en lettere måde at filtrere beskeder på. MBassador understøtter et undersæt af Java EL-udtryk som betingelser for filtrering af meddelelser.

Lad os filtrere en Snor besked baseret på dens længde:

private String testString; @Test offentlig ugyldig nårLongStringDispatched_thenStringFiltered () {dispatcher.post ("foobar!"). Nu (); assertNull (testString); } @Handler (condition = "msg.length () <7") public void handleStringMessage (String message) {this.testString = message; }

Den "foobar!" beskeden er syv tegn lang og filtreres. Lad os sende en kortere Snor:

 @Test offentlig ugyldig nårShortStringDispatched_thenStringHandled () {dispatcher.post ("foobar"). Nu (); assertNotNull (testString); }

Nu er “foobar” kun seks tegn lang og sendes igennem.

Vores Afvis besked indeholder et felt med en accessor. Lad os skrive et filter til det:

private RejectMessage rejectMessage; @Test offentlig ugyldig nårWrongRejectDispatched_thenRejectFiltered () {RejectMessage testReject = new RejectMessage (); testReject.setCode (-1); dispatcher.post (testReject) .now (); assertNull (afvis besked); assertNotNull (subMessage); assertEquals (-1, ((RejectMessage) subMessage) .getCode ()); } @Handler (betingelse = "msg.getCode ()! = -1") public void handleRejectMessage (RejectMessage rejectMessage) {this.rejectMessage = rejectMessage; }

Her igen kan vi forespørge om en metode på et objekt og enten filtrere meddelelsen eller ej.

5.4. Optag filtrerede meddelelser

Svarende til DeadEvents, vi vil muligvis indfange og behandle filtrerede meddelelser. Der er også en dedikeret mekanisme til at registrere filtrerede begivenheder. Filtrerede begivenheder behandles forskelligt fra "døde" begivenheder.

Lad os skrive en test, der illustrerer dette:

private String testString; private FilteredMessage filteredMessage; private DeadMessage deadMessage; @Test offentlig ugyldig nårLongStringDispatched_thenStringFiltered () {dispatcher.post ("foobar!"). Nu (); assertNull (testString); assertNotNull (filteredMessage); assertTrue (filteredMessage.getMessage () instans af streng); assertNull (deadMessage); } @Handler (condition = "msg.length () <7") public void handleStringMessage (String message) {this.testString = message; } @ Handler offentlig ugyldig handleFilterMessage (FilteredMessage meddelelse) {this.filteredMessage = meddelelse; } @Handler offentligt ugyldigt handleDeadMessage (DeadMessage deadMessage) {this.deadMessage = deadMessage; } 

Med tilføjelsen af ​​en FilteredMessage handler, kan vi spore Strenge der filtreres på grund af deres længde. Det filterMessage indeholder vores for lange Snor mens deadMessage forbliver nul.

6. Afsendelse og håndtering af asynkron meddelelse

Indtil videre har alle vores eksempler brugt synkron afsendelse af meddelelser; da vi ringede post.now () meddelelserne blev leveret til hver handler i den samme tråd, vi ringede til stolpe() fra.

6.1. Asynkron forsendelse

Det MBassador.post () returnerer en SyncAsyncPostCommand. Denne klasse tilbyder flere metoder, herunder:

  • nu() - sende meddelelser synkront; opkaldet spærres, indtil alle meddelelser er leveret
  • asynkront () - udfører meddelelsespublikationen asynkront

Lad os bruge asynkron forsendelse i en prøveklasse. Vi bruger Awaitility i disse tests til at forenkle koden:

privat MBassador afsender = ny MBassador (); private String testString; privat AtomicBoolean klar = ny AtomicBoolean (falsk); @Test offentligt ugyldigt nårAsyncDispatched_thenMessageReceived () {dispatcher.post ("foobar"). Asynkront (); afvente (). indtil Atomisk (klar, lige til (sand)); assertNotNull (testString); } @Handler offentlig ugyldig handleStringMessage (streng besked) {this.testString = besked; ready.set (sand); }

Vi ringer asynkront () i denne test, og brug en AtomicBoolean som et flag med vente() at vente på, at leveringstråden leverer beskeden.

Hvis vi kommenterer opkaldet til vente(), vi risikerer, at testen mislykkes, fordi vi kontrollerer testString inden leveringstråden er afsluttet.

6.2. Asynkron håndtering af indkaldelse

Asynkron forsendelse giver meddelelsesudbyderen mulighed for at vende tilbage til beskedbehandling, før meddelelserne leveres til hver behandler, men den kalder stadig hver behandler i rækkefølge, og hver behandler skal vente på, at den foregående er færdig.

Dette kan føre til problemer, hvis en handler udfører en dyr operation.

MBassador tilvejebringer en mekanisme til asynkron påkaldelse af handler. Handlere, der er konfigureret til dette, modtager beskeder i deres tråd:

privat Integer testInteger; privat streng invocationThreadName; privat AtomicBoolean klar = ny AtomicBoolean (falsk); @Test offentlig ugyldig nårHandlerAsync_thenHandled () {dispatcher.post (42) .now (); afvente (). indtil Atomisk (klar, lige til (sand)); assertNotNull (testInteger); assertFalse (Thread.currentThread (). getName (). er lig med (invocationThreadName)); } @Handler (levering = Invoke.Asynchronously) public void handleIntegerMessage (Integer message) {this.invocationThreadName = Thread.currentThread (). GetName (); this.testInteger = besked; ready.set (sand); }

Handlere kan anmode om asynkron påkaldelse med levering = Invok.Asynkront ejendom på Handler kommentar. Vi bekræfter dette i vores test ved at sammenligne Tråd navne i afsendelsesmetoden og handler.

7. Tilpasning af MBassador

Indtil videre har vi brugt en forekomst af MBassador med sin standardkonfiguration. Afsenderens adfærd kan modificeres med kommentarer, svarende til dem, vi hidtil har set; vi dækker et par mere for at afslutte denne tutorial.

7.1. Undtagelse Håndtering

Handlere kan ikke definere afkrydsede undtagelser. I stedet kan afsenderen forsynes med en IPublicationErrorHandler som et argument til sin konstruktør:

offentlig klasse MBassadorConfigurationTest implementerer IPublicationErrorHandler {privat MBassador dispatcher; privat String beskedString; privat kastbar fejl Årsag; @Før offentlige ugyldige prepareTests () {dispatcher = ny MBassador (dette); dispatcher.subscribe (dette); } @Test offentlig ugyldig nårErrorOccurs_thenErrorHandler () {dispatcher.post ("Fejl"). Nu (); assertNull (messageString); assertNotNull (errorCause); } @Test offentlig ugyldig nårNoErrorOccurs_thenStringHandler () {dispatcher.post ("Fejl"). Nu (); assertNull (errorCause); assertNotNull (messageString); } @Handler public void handleString (String message) {if ("Error" .equals (message)) {throw new Error ("BOOM"); } messageString = besked; } @ Overstyr offentlig ugyldig handleError (PublicationError-fejl) {errorCause = error.getCause (). GetCause (); }}

Hvornår handleString () kaster en Fejl, det gemmes i fejl Årsag.

7.2. Handlerprioritet

Handlere kaldes i omvendt rækkefølge af, hvordan de tilføjes, men dette er ikke en adfærd, vi vil stole på. Selv med evnen til at ringe til håndtere i deres tråde, er vi muligvis stadig nødt til at vide, hvilken rækkefølge de vil blive kaldt til.

Vi kan indstille handlerprioritet eksplicit:

privat LinkedList liste = ny LinkedList (); @Test offentlig ugyldig nårRejectDispatched_thenPriorityHandled () {dispatcher.post (ny RejectMessage ()). Nu (); // Elementer skal poppe () af i omvendt prioritetsrækkefølge assertTrue (1 == list.pop ()); assertTrue (3 == list.pop ()); assertTrue (5 == list.pop ()); } @Handler (prioritet = 5) offentlig ugyldig handleRejectMessage5 (RejectMessage rejectMessage) {list.push (5); } @Handler (prioritet = 3) offentlig ugyldig handleRejectMessage3 (RejectMessage rejectMessage) {list.push (3); } @Handler (prioritet = 2, rejectSubtypes = sand) public void handleMessage (Message rejectMessage) logger.error ("Afvis handler nr. 3"); list.push (3); } @Handler (prioritet = 0) offentlig ugyldig handleRejectMessage0 (RejectMessage rejectMessage) {list.push (1); } 

Handlere kaldes fra højeste prioritet til laveste. Handlere med standardprioriteten, som er nul, kaldes sidste. Vi ser, at handlernummeret pop () slukket i omvendt rækkefølge.

7.3. Afvis undertyper, den nemme måde

Hvad skete der med handleMessage () i testen ovenfor? Vi behøver ikke bruge RejectSubTypes.class for at filtrere vores undertyper.

RejectSubTypes er et boolesk flag, der giver samme filtrering som klassen, men med bedre ydeevne end IMessageFilter implementering.

Vi har stadig brug for den filterbaserede implementering til kun at acceptere undertyper.

8. Konklusion

MBassador er et simpelt og ligetil bibliotek til at sende meddelelser mellem objekter. Beskeder kan organiseres på forskellige måder og kan afsendes synkront eller asynkront.

Og som altid er eksemplet tilgængeligt i dette GitHub-projekt.


$config[zx-auto] not found$config[zx-overlay] not found