Introduksjon til MBassador

1. Oversikt

Enkelt sagt, MBassador er det en høytytende arrangementbuss som bruker semantikken for publiser-abonnement.

Meldinger sendes til en eller flere jevnaldrende uten forkunnskap om hvor mange abonnenter det er, eller hvordan de bruker meldingen.

2. Maven avhengighet

Før vi kan bruke biblioteket, må vi legge til ambassadøravhengighet:

 net.engio ambassadør 1.3.1 

3. Grunnleggende håndtering av hendelser

3.1. Enkelt eksempel

Vi begynner med et enkelt eksempel på publisering av en melding:

privat MBassador-utsender = ny MBassador (); private String messageString; @Før offentlige ugyldige preparatester () {dispatcher.subscribe (dette); } @Test offentlig ugyldig nårStringDispatched_thenHandleString () {dispatcher.post ("TestString"). Nå (); assertNotNull (messageString); assertEquals ("TestString", messageString); } @Handler offentlig ugyldig handleString (strengmelding) {messageString = melding; } 

På toppen av denne testklassen ser vi etableringen av en MBassador med standardkonstruktøren. Neste, i @Før metode, kaller vi abonnere() og gi en referanse til selve klassen.

I abonnere(), senderen inspiserer abonnenten for @Handler kommentarer.

Og i den første testen ringer vi dispatcher.post (…) .now () å sende meldingen - som resulterer i handleString () blir kalt.

Denne første testen demonstrerer flere viktige konsepter. Noen Gjenstand kan være abonnent, så lenge den har en eller flere metoder kommentert med @Handler. En abonnent kan ha et hvilket som helst antall håndtere.

Vi bruker testobjekter som abonnerer på seg selv for enkelhets skyld, men i de fleste produksjonsscenarier vil meldingsutsendere i forskjellige klasser fra forbrukere.

Håndteringsmetoder har bare én inndataparameter - meldingen og kan ikke kaste noen avmerkede unntak.

I likhet med abonnere() metode godtar innleggsmetoden noen Gjenstand. Dette Gjenstand leveres til abonnenter.

Når en melding blir lagt ut, blir den levert til alle lyttere som har abonnert på meldingstypen.

La oss legge til en annen meldingsbehandler og sende en annen meldingstype:

privat HeltallmeldingHeltall; @Test offentlig ugyldig nårIntegerDispatched_thenHandleInteger () {dispatcher.post (42) .now (); assertNull (messageString); assertNotNull (messageInteger); assertTrue (42 == messageInteger); } @Handler offentlig ugyldig handleInteger (Heltallmelding) {messageInteger = melding; } 

Som forventet, når vi senderen Heltall, handleInteger () kalles, og handleString () er ikke. En enkelt avsender kan brukes til å sende mer enn én meldingstype.

3.2. Døde meldinger

Så hvor går en melding når det ikke er noen behandler for den? La oss legge til en ny hendelsesbehandler og deretter sende en tredje meldingstype:

private Objekt deadEvent; @Test offentlig ugyldig nårLongDispatched_thenDeadEvent () {dispatcher.post (42L) .now (); assertNull (messageString); assertNull (messageInteger); assertNotNull (deadEvent); assertTrue (deadEvent instanceof Long); assertTrue (42L == (Long) deadEvent); } @Handler offentlig ugyldig handleDeadEvent (DeadMessage-melding) {deadEvent = message.getMessage (); } 

I denne testen sender vi a Lang i stedet for en Heltall. Ingen handleInteger () heller ikke handleString () kalles, men handleDeadEvent () er.

Når det ikke er håndtere for en melding, blir den pakket inn i en DeadMessage gjenstand. Siden vi la til en handler for Deadmessage, vi fanger den.

DeadMessage kan trygt ignoreres; hvis en applikasjon ikke trenger å spore døde meldinger, kan de få lov til å gå noe sted.

4. Bruke et hendelseshierarki

Sender String og Heltall hendelser er begrensende. La oss lage noen meldingsklasser:

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

Vi har en enkel baseklasse og to klasser som utvider den.

4.1. Sende en basisklasse Beskjed

Vi begynner med Beskjed arrangementer:

privat MBassador-utsender = ny MBassador (); privat melding private AckMessage ackMessage; private RejectMessage rejectMessage; @Før offentlige ugyldige preparatester () {dispatcher.subscribe (dette); } @Test offentlig ugyldig nårMessageDispatched_thenMessageHandled () {dispatcher.post (ny melding ()). Nå (); assertNotNull (melding); assertNull (ackMessage); assertNull (rejectMessage); } @Handler public void handleMessage (Message message) {this.message = message; } @Handler offentlig ugyldig handleRejectMessage (RejectMessage melding) {rejectMessage = melding; } @Handler offentlig ugyldig handleAckMessage (AckMessage-melding) {ackMessage = melding; }

Oppdag MBassador - en høytytende pub-sub-arrangementbuss. Dette begrenser oss til å bruke Meldinger men legger til et ekstra lag av typesikkerhet.

Når vi sender en Beskjed, handleMessage () mottar den. De to andre håndtererne gjør det ikke.

4.2. Sende en underklassemelding

La oss sende en Avvis melding:

@Test offentlig ugyldig nårRejectDispatched_thenMessageAndRejectHandled () {dispatcher.post (ny RejectMessage ()). Nå (); assertNotNull (melding); assertNotNull (rejectMessage); assertNull (ackMessage); }

Når vi sender en Avvis melding både handleRejectMessage () og handleMessage () motta det.

Siden Avvis melding strekker Beskjed, de Beskjed behandler mottok den, i tillegg til RejectMessage behandler.

La oss bekrefte denne oppførselen med en AckMessage:

@Test offentlig ugyldig nårAckDispatched_thenMessageAndAckHandled () {dispatcher.post (new AckMessage ()). Now (); assertNotNull (melding); assertNotNull (ackMessage); assertNull (rejectMessage); }

Akkurat som vi forventet, når vi sender en AckMessage, både handleAckMessage () og handleMessage () motta det.

5. Filtrering av meldinger

Å organisere meldinger etter type er allerede en kraftig funksjon, men vi kan filtrere dem enda mer.

5.1. Filtrer på klasse og underklasse

Da vi la ut en Avvis melding eller AckMessage, mottok vi arrangementet både i hendelsesbehandleren for den bestemte typen og i basisklassen.

Vi kan løse denne typen hierarkiproblemer ved å lage Beskjed abstrakt og skape en klasse som GenericMessage. Men hva om vi ikke har denne luksusen?

Vi kan bruke meldingsfiltre:

privat melding baseMessage; privat melding subMessage; @Test offentlig ugyldig nårMessageDispatched_thenMessageFiltered () {dispatcher.post (ny melding ()). Nå (); assertNotNull (baseMessage); assertNull (undermelding); } @Test offentlig ugyldig nårRejectDispatched_thenRejectFiltered () {dispatcher.post (new RejectMessage ()). Now (); assertNotNull (subMessage); assertNull (baseMessage); } @Handler (filters = {@Filter (Filters.RejectSubtypes.class)}) public void handleBaseMessage (Message message) {this.baseMessage = message; } @Handler (filters = {@Filter (Filters.SubtypesOnly.class)}) public void handleSubMessage (Message message) {this.subMessage = message; }

De filtre parameter for @Handler merknader godtar a Klasse som implementerer IMessageFilter. Biblioteket tilbyr to eksempler:

De Filters.RejectSubtypes gjør som navnet antyder: det vil filtrere ut eventuelle undertyper. I dette tilfellet ser vi det Avvis melding håndteres ikke av handleBaseMessage ().

De Filtre. Undertyper bare gjør også som navnet antyder: det vil filtrere ut alle basetyper. I dette tilfellet ser vi det Beskjed håndteres ikke av handleSubMessage ().

5.2. IMessageFilter

De Filters.RejectSubtypes og Filtre. Undertyper bare begge implementerer IMessageFilter.

RejectSubTypes sammenligner klassen av meldingen med de definerte meldingstypene og vil bare tillate gjennom meldinger som tilsvarer en av typene, i motsetning til alle underklasser.

5.3. Filtrer med forhold

Heldigvis er det en enklere måte å filtrere meldinger på. MBassador støtter et delsett av Java EL-uttrykk som betingelser for filtrering av meldinger.

La oss filtrere a String melding basert på lengden:

private String testString; @Test offentlig ugyldig nårLongStringDispatched_thenStringFiltered () {dispatcher.post ("foobar!"). Nå (); assertNull (testString); } @Handler (betingelse = "msg.length () <7") offentlig ugyldig handleStringMessage (strengmelding) {this.testString = melding; }

"Foobar!" meldingen er syv tegn lang og er filtrert. La oss sende en kortere String:

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

Nå er “foobar” bare seks tegn lang og går gjennom.

Våre Avvis melding inneholder et felt med en accessor. La oss skrive et filter for det:

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

Her igjen kan vi spørre om en metode på et objekt og enten filtrere meldingen eller ikke.

5.4. Ta opp filtrerte meldinger

Lik DeadEvents, det kan være lurt å fange og behandle filtrerte meldinger. Det er en dedikert mekanisme for å fange opp filtrerte hendelser også. Filtrerte hendelser behandles forskjellig fra "døde" hendelser.

La oss skrive en test som illustrerer dette:

private String testString; private FilteredMessage filteredMessage; private DeadMessage deadMessage; @Test offentlig ugyldig nårLongStringDispatched_thenStringFiltered () {dispatcher.post ("foobar!"). Nå (); assertNull (testString); assertNotNull (filteredMessage); assertTrue (filteredMessage.getMessage () forekomst av streng); assertNull (deadMessage); } @Handler (betingelse = "msg.length () <7") offentlig ugyldig handleStringMessage (strengmelding) {this.testString = melding; } @ Handler offentlig ugyldig handleFilterMessage (FilteredMessage melding) {this.filteredMessage = melding; } @Handler offentlig ugyldig handleDeadMessage (DeadMessage deadMessage) {this.deadMessage = deadMessage; } 

Med tillegg av en FilteredMessage handler, kan vi spore Strenger som er filtrert på grunn av lengden. De filterMessage inneholder for lang String samtidig som deadMessage rester null.

6. Asynkron sending og håndtering av meldinger

Så langt har alle eksemplene våre brukt synkron meldingssending; da vi ringte post.now () meldingene ble levert til hver behandler i samme tråd som vi ringte post() fra.

6.1. Asynkron forsendelse

De MBassador.post () returnerer en SyncAsyncPostCommand. Denne klassen tilbyr flere metoder, inkludert:

  • nå() - sende meldinger synkront; samtalen blokkeres til alle meldingene er levert
  • asynkront () - utfører meldingspublikasjonen asynkront

La oss bruke asynkron forsendelse i en prøveklasse. Vi bruker Awaitility i disse testene for å forenkle koden:

privat MBassador-utsender = ny MBassador (); private String testString; private AtomicBoolean ready = nye AtomicBoolean (false); @Test offentlig ugyldig nårAsyncDispatched_thenMessageReceived () {dispatcher.post ("foobar"). Asynkront (); avvente () .tilAtomic (klar, likeTo (sann)); assertNotNull (testString); } @Handler offentlig ugyldig handleStringMessage (strengmelding) {this.testString = melding; ready.set (true); }

Vi ringer asynkront () i denne testen, og bruk en AtomicBoolean som et flagg med avvente() å vente på leveringstråden for å levere meldingen.

Hvis vi kommenterer samtalen til avvente(), vi risikerer at testen mislykkes, fordi vi sjekker testString før leveringstråden er fullført.

6.2. Asynkron håndtererpåkallelse

Asynkron forsendelse tillater meldeleverandøren å gå tilbake til meldingsbehandling før meldingene blir levert til hver behandler, men den ringer fortsatt hver behandler i rekkefølge, og hver behandler må vente til den forrige er ferdig.

Dette kan føre til problemer hvis en behandler utfører en kostbar operasjon.

MBassador tilbyr en mekanisme for påkallelse av asynkron handler. Handlere som er konfigurert for dette, mottar meldinger i tråden:

privat HeltallstestHeltall; privat strenginnkallelseTrådnavn; private AtomicBoolean ready = nye AtomicBoolean (false); @Test offentlig ugyldig nårHandlerAsync_thenHandled () {dispatcher.post (42) .now (); avvente () .tilAtomic (klar, likeTo (sann)); assertNotNull (testInteger); assertFalse (Thread.currentThread (). getName (). er lik (invocationThreadName)); } @Handler (levering = Invoke.Asynchronously) offentlig ugyldig handleIntegerMessage (Integer melding) {this.invocationThreadName = Thread.currentThread (). GetName (); this.testInteger = melding; ready.set (true); }

Handlere kan be om asynkron påkallelse med levering = Påkalle.Asynkront eiendom på Behandler kommentar. Vi bekrefter dette i testen ved å sammenligne Tråd navn i utsendelsesmetoden og behandleren.

7. Tilpasse MBassador

Så langt har vi brukt en forekomst av MBassador med standardkonfigurasjonen. Senderens oppførsel kan modifiseres med merknader, som ligner på de vi har sett så langt; vi dekker noen flere for å fullføre denne opplæringen.

7.1. Avvikshåndtering

Behandlere kan ikke definere avmerkede unntak. I stedet kan avsenderen få en IPublicationErrorHandler som et argument til konstruktøren:

offentlig klasse MBassadorConfigurationTest implementerer IPublicationErrorHandler {private MBassador dispatcher; private String messageString; private Kastbar feil Årsak; @Før offentlige ugyldige preparatester () {dispatcher = ny MBassador (dette); dispatcher. abonner (dette); } @Test offentlig ugyldig nårErrorOccurs_thenErrorHandler () {dispatcher.post ("Feil"). Nå (); assertNull (messageString); assertNotNull (errorCause); } @Test offentlig ugyldig nårNoErrorOccurs_thenStringHandler () {dispatcher.post ("Feil"). Nå (); assertNull (errorCause); assertNotNull (messageString); } @Handler offentlig ugyldig handleString (strengmelding) {if ("Feil". Lik (melding)) {kast ny feil ("BOOM"); } messageString = melding; } @Override public void handleError (PublicationError error) {errorCause = error.getCause (). GetCause (); }}

Når handleString () kaster en Feil, den er lagret til errorCause.

7.2. Handlerprioritet

Handlere kalles i omvendt rekkefølge av hvordan de legges til, men dette er ikke oppførsel vi vil stole på. Selv med muligheten til å ringe håndtere i trådene, kan det hende at vi fortsatt trenger å vite hvilken rekkefølge de vil bli kalt inn.

Vi kan angi behandlerprioritet eksplisitt:

privat LinkedList-liste = ny LinkedList (); @Test offentlig ugyldig nårRejectDispatched_thenPriorityHandled () {dispatcher.post (new RejectMessage ()). Now (); // Elementer skal poppe () av ​​i omvendt prioritetsrekkefø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 = true) public void handleMessage (Message rejectMessage) logger.error ("Avvis behandler nr. 3"); list.push (3); } @Handler (prioritet = 0) offentlig ugyldig handleRejectMessage0 (RejectMessage rejectMessage) {list.push (1); } 

Handlere blir kalt fra høyeste prioritet til laveste. Handlere med standardprioritet, som er null, kalles sist. Vi ser at handlernumrene pop () av i omvendt rekkefølge.

7.3. Avvis undertyper, den enkle måten

hva skjedde med handleMessage () i testen over? Vi trenger ikke bruke RejectSubTypes.class for å filtrere undertypene våre.

RejectSubTypes er et boolsk flagg som gir samme filtrering som klassen, men med bedre ytelse enn IMessageFilter gjennomføring.

Vi må fremdeles bare bruke den filterbaserte implementeringen for å godta undertyper.

8. Konklusjon

MBassador er et enkelt og greit bibliotek for overføring av meldinger mellom objekter. Meldinger kan organiseres på en rekke måter og kan sendes synkront eller asynkront.

Og som alltid er eksemplet tilgjengelig i dette GitHub-prosjektet.


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