En guide til Spring State Machine Project

1. Introduksjon

Denne artikkelen er fokusert på Spring's State Machine-prosjekt - som kan brukes til å representere arbeidsflyter eller andre former for endelige representasjonsproblemer med statsautomater.

2. Maven avhengighet

For å komme i gang, må vi legge til hovedavhengigheten til Maven:

 org.springframework.statemachine spring-statemachine-core 1.2.3.RELEASE 

Den siste versjonen av denne avhengigheten finner du her.

3. Oppgi maskinkonfigurasjon

La oss nå komme i gang med å definere en enkel tilstandsmaskin:

@Configuration @EnableStateMachine offentlig klasse SimpleStateMachineConfiguration utvider StateMachineConfigurerAdapter {@Override public void configure (StateMachineStateConfigurer states) kaster Exception {states .withStates () .initial ("SI") .end ("SF") .states (Hash. ("S1", "S2", "S3"))); } @ Override public void configure (StateMachineTransitionConfigurer transitions) kaster Unntak {transitions.withExternal (). Kilde ("SI"). Target ("S1"). Hendelse ("E1"). Og () .withExternal (). Kilde "S1"). Target ("S2"). Event ("E2"). Og () .withExternal () .source ("S2"). Target ("SF"). Event ("end"); }}

Merk at denne klassen er merket som en konvensjonell fjærkonfigurasjon så vel som en tilstandsmaskin. Det må også utvides StateMachineConfigurerAdapter slik at ulike initialiseringsmetoder kan påberopes. I en av konfigurasjonsmetodene definerer vi alle de mulige tilstandene til tilstandsmaskinen, i den andre hvordan hendelser endrer den nåværende tilstanden.

Konfigurasjonen ovenfor viser en ganske enkel, rettlinjet overgangstilstandsmaskin som skal være lett nok å følge.

Nå må vi starte en vårkontekst og få en referanse til tilstandsmaskinen definert av vår konfigurasjon:

@Autowired private StateMachine stateMachine;

Når vi har statsmaskinen, må den startes:

stateMachine.start ();

Nå som maskinen vår er i opprinnelig tilstand, kan vi sende hendelser og dermed utløse overganger:

stateMachine.sendEvent ("E1");

Vi kan alltid sjekke den nåværende tilstanden til statsmaskinen:

stateMachine.getState ();

4. Handlinger

La oss legge til noen handlinger som skal utføres rundt statsoverganger. Først definerer vi handlingen vår som en vårbønne i samme konfigurasjonsfil:

@Bean public Action initAction () {return ctx -> System.out.println (ctx.getTarget (). GetId ()); }

Deretter kan vi registrere handlingen ovenfor som er opprettet på overgangen i vår konfigurasjonsklasse:

@ Override public void configure (StateMachineTransitionConfigurer overganger) kaster Unntak {transitions.withExternal () transitions.withExternal (). Kilde ("SI"). Target ("S1"). Event ("E1"). Handling (initAction ())

Denne handlingen vil bli utført når overgangen fra SI til S1 via arrangement E1 inntreffer. Handlinger kan knyttes til statene selv:

@Bean offentlig handling executeAction () {return ctx -> System.out.println ("Gjør" + ctx.getTarget (). GetId ()); } sier .withStates () .state ("S3", executeAction (), errorAction ());

Denne tilstandsdefinisjonsfunksjonen aksepterer en operasjon som skal utføres når maskinen er i måltilstand, og eventuelt en feilhandlingsbehandler.

En feilhandlingsbehandler er ikke mye forskjellig fra noen annen handling, men den vil bli påkalt hvis et unntak blir kastet når som helst under evalueringen av statens handlinger:

@Bean public Action errorAction () {return ctx -> System.out.println ("Error" + ctx.getSource (). GetId () + ctx.getException ()); }

Det er også mulig å registrere individuelle handlinger for inngang, gjøre og exit tilstandsoverganger:

@Bean public Action entryAction () {return ctx -> System.out.println ("Entry" + ctx.getTarget (). GetId ()); } @Bean public Action executeAction () {return ctx -> System.out.println ("Do" + ctx.getTarget (). GetId ()); } @Bean offentlig handling exitAction () {return ctx -> System.out.println ("Exit" + ctx.getSource (). GetId () + "->" + ctx.getTarget (). GetId ()); }
stater .withStates () .stateEntry ("S3", entryAction ()) .stateDo ("S3", executeAction ()) .stateExit ("S3", exitAction ());

Respektive handlinger vil bli utført på tilsvarende tilstandsoverganger. For eksempel vil vi kanskje bekrefte noen forutsetninger på tidspunktet for innreise eller utløse rapportering på tidspunktet for utgangen.

5. Globale lyttere

Globale hendelseslyttere kan defineres for statsmaskinen. Disse lytterne vil bli påkalt når som helst en tilstandsovergang forekommer og kan brukes til ting som logging eller sikkerhet.

Først må vi legge til en annen konfigurasjonsmetode - en som ikke håndterer tilstander eller overganger, men med konfigurasjonen for selve statsmaskinen.

Vi må definere en lytter ved å utvide den StateMachineListenerAdapter:

offentlig klasse StateMachineListener utvider StateMachineListenerAdapter {@Override public void stateChanged (State from, State to) {System.out.printf ("Overført fra% s til% s% n", fra == null? "none": from.getId ( ), to.getId ()); }}

Her overstyrer vi bare stateChanged selv om mange andre jevne kroker er tilgjengelige.

6. Utvidet stat

Spring State Machine holder rede på sin tilstand, men for å holde styr på vår applikasjon tilstand, det være seg noen beregnede verdier, oppføringer fra administratorer eller svar fra kaller eksterne systemer, må vi bruke det som kalles en utvidet tilstand.

Anta at vi vil sørge for at en kontosøknad går gjennom to godkjenningsnivåer. Vi kan holde oversikt over godkjenningstall ved hjelp av et heltall lagret i utvidet tilstand:

@Bean offentlig handling executeAction () {return ctx -> {int approvals = (int) ctx.getExtendedState (). GetVariables () .getOrDefault ("approvalCount", 0); godkjenninger ++; ctx.getExtendedState (). getVariables () .put ("approvalCount", godkjenninger); }; }

7. Vakter

En vakt kan brukes til å validere noen data før en overgang til en stat utføres. En vakt ser veldig ut som en handling:

@Bean public Guard simpleGuard () {return ctx -> (int) ctx.getExtendedState () .getVariables () .getOrDefault ("approvalCount", 0)> 0; }

Den merkbare forskjellen her er at en vakt returnerer a ekte eller falsk som vil informere statsmaskinen om overgangen skal tillates å skje.

Støtte for SPeL-uttrykk som vakter eksisterer også. Eksemplet ovenfor kunne også ha blitt skrevet som:

.guardExpression ("extendedState.variables.approvalCount> 0")

8. Oppgi maskin fra en byggmester

StateMachineBuilder kan brukes til å lage en tilstandsmaskin uten å bruke vårkommentarer eller opprette en vårkontekst:

StateMachineBuilder.Builder builder = StateMachineBuilder.builder (); builder.configureStates (). withStates () .initial ("SI") .state ("S1"). end ("SF"); builder.configureTransitions () .withExternal () .source ("SI"). target ("S1"). event ("E1"). and (). withExternal (). source ("S1"). target ("SF ") .event (" E2 "); StateMachine machine = builder.build ();

9. Hierarkiske stater

Hierarkiske tilstander kan konfigureres ved å bruke flere withStates () i forbindelse med foreldre ():

sier .withStates () .initial ("SI") .state ("SI") .end ("SF"). og () .withStates () .parent ("SI") .initial ("SUB1") .state ("SUB2") .end ("SEND");

Denne typen oppsett lar statsmaskinen ha flere tilstander, så en oppfordring til getState () vil produsere flere IDer. For eksempel, umiddelbart etter oppstart resulterer følgende uttrykk i:

stateMachine.getState (). getIds () ["SI", "SUB1"]

10. Kryss (valg)

Så langt har vi laget tilstandsoverganger som var lineære av natur. Ikke bare er dette ganske uinteressant, men det gjenspeiler heller ikke virkelige brukssaker som en utvikler vil bli bedt om å implementere heller. Oddsen er at betingede veier må implementeres, og Spring state machine's kryss (eller valg) tillater oss å gjøre nettopp det.

Først må vi markere en tilstand som et veikryss (valg) i tilstandsdefinisjonen:

sier .withStates () .junction ("SJ")

Så i overgangene definerer vi først / deretter / siste alternativer som tilsvarer en if-then-else struktur:

.withJunction () .source ("SJ") .first ("high", highGuard ()) .then ("medium", mediumGuard ()) .last ("low")

først og deretter ta et annet argument som er en vanlig vakt som vil bli påkalt for å finne ut hvilken vei du skal ta:

@Bean public Guard mediumGuard () {return ctx -> false; } @Bean public Guard highGuard () {return ctx -> false; }

Merk at en overgang ikke stopper ved en kryssknutepunkt, men vil umiddelbart utføre definerte vakter og gå til en av de angitte rutene.

I eksemplet ovenfor vil instruere tilstandsmaskinen om å overgå til SJ føre til at den faktiske tilstanden blir lav da begge vaktene bare returnerer falske.

En siste merknad er at API gir både kryss og valg. Men funksjonelt er de identiske i alle aspekter.

11. Gaffel

Noen ganger blir det nødvendig å dele kjøringen i flere uavhengige kjøringsveier. Dette kan oppnås ved hjelp av gaffel funksjonalitet.

Først må vi betegne en node som en gaffelnode og opprette hierarkiske regioner som statsmaskinen vil utføre delingen i:

sier .withStates () .initial ("SI") .fork ("SFork"). og () .withStates () .parent ("SFork") .initial ("Sub1-1") .end ("Sub1-2 ") .and () .withStates () .parent (" SFork "). initial (" Sub2-1 ") .end (" Sub2-2 ");

Definer deretter gaffelovergang:

.withFork () .source ("SFork") .target ("Sub1-1") .target ("Sub2-1");

12. Bli med

Komplementet til gaffeloperasjonen er sammenføyningen. Det lar oss sette en tilstand som går over til som er avhengig av å fullføre noen andre tilstander:

Som med forking, må vi angi en sammenføyningsknute i tilstandsdefinisjonen:

stater .withStates () .join ("SJoin")

Så i overganger definerer vi hvilke stater som må fullføres for å aktivere vår sammenkoblingsstatus:

overganger .withJoin () .source ("Sub1-2") .source ("Sub2-2") .target ("SJoin");

Det er det! Med denne konfigurasjonen, når begge Sub1-2 og Sub2-2 oppnås, vil statsmaskinen overgå til S Bli med

13. Enums I stedet for Strenger

I eksemplene ovenfor har vi brukt strengkonstanter for å definere tilstander og hendelser for klarhet og enkelhet. På et ekte produksjonssystem vil man sannsynligvis ønske å bruke Java's enums for å unngå stavefeil og få mer typesikkerhet.

Først må vi definere alle mulige tilstander og hendelser i systemet vårt:

public enum ApplicationReviewStates {PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED} public enum ApplicationReviewEvents {APPROVE, REJECT}

Vi må også passere enumene våre som generiske parametere når vi utvider konfigurasjonen:

offentlig klasse SimpleEnumStateMachineConfiguration utvider StateMachineConfigurerAdapter 

Når vi er definert, kan vi bruke våre enum-konstanter i stedet for strenger. For eksempel for å definere en overgang:

transitions.withExternal () .source (ApplicationReviewStates.PEER_REVIEW) .target (ApplicationReviewStates.PRINCIPAL_REVIEW) .event (ApplicationReviewEvents.APPROVE)

14. Konklusjon

Denne artikkelen utforsket noen av funksjonene til Spring State Machine.

Som alltid kan du finne eksemplet på kildekoden på GitHub.


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