En guide til Spring State Machine Project

1. Introduktion

Denne artikel er fokuseret på Spring's State Machine-projekt - som kan bruges til at repræsentere arbejdsgange eller andre former for endelige problemer med repræsentation af statsautomater.

2. Maven-afhængighed

For at komme i gang skal vi tilføje den vigtigste Maven-afhængighed:

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

Den seneste version af denne afhængighed kan findes her.

3. Angiv maskinens konfiguration

Lad os nu komme i gang ved at definere en simpel tilstandsmaskine:

@Configuration @EnableStateMachine offentlig klasse SimpleStateMachineConfiguration udvider StateMachineConfigurerAdapter {@Override public void configure (StateMachineStateConfigurer states) throw Exception {states .withStates () .initial ("SI") .end ("SF") .States (Hash. ("S1", "S2", "S3"))); } @ Override offentlig ugyldig konfiguration (StateMachineTransitionConfigurer overgange) kaster undtagelse {transitions.withExternal (). Kilde ("SI"). Target ("S1"). Begivenhed ("E1"). Og () .withExternal (). Kilde ( "S1"). Mål ("S2"). Begivenhed ("E2"). Og () .withExternal (). Kilde ("S2"). Mål ("SF"). Begivenhed ("slut"); }}

Bemærk, at denne klasse er kommenteret som en konventionel fjederkonfiguration såvel som en statsmaskine. Det skal også udvides StateMachineConfigurerAdapter så forskellige initialiseringsmetoder kan påberåbes. I en af ​​konfigurationsmetoderne definerer vi alle de mulige tilstande for tilstandsmaskinen, i den anden, hvordan hændelser ændrer den aktuelle tilstand.

Ovenstående konfiguration angiver en ret simpel, lineær overgangstilstandsmaskine, som skal være let nok at følge.

Nu er vi nødt til at starte en Spring-kontekst og få en reference til tilstandsmaskinen defineret af vores konfiguration:

@Autowired privat StateMachine stateMachine;

Når vi har statsmaskinen, skal den startes:

stateMachine.start ();

Nu hvor vores maskine er i den oprindelige tilstand, kan vi sende begivenheder og dermed udløse overgange:

stateMachine.sendEvent ("E1");

Vi kan altid kontrollere tilstandsmaskinens aktuelle tilstand:

stateMachine.getState ();

4. Handlinger

Lad os tilføje nogle handlinger, der skal udføres omkring statsovergange. Først definerer vi vores handling som en springbønne i den samme konfigurationsfil:

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

Derefter kan vi registrere ovenstående oprettede handling på overgangen i vores konfigurationsklasse:

@ Override offentlig ugyldig konfiguration (StateMachineTransitionConfigurer overgange) kaster Undtagelse {transitions.withExternal () transitions.withExternal (). Kilde ("SI"). Target ("S1"). Event ("E1"). Handling (initAction ())

Denne handling udføres, når overgangen fra SI til S1 via begivenhed E1 opstår. Handlinger kan knyttes til staterne selv:

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

Denne tilstandsdefinitionsfunktion accepterer en operation, der skal udføres, når maskinen er i måltilstand og eventuelt en fejlhandlingshåndterer.

En fejlhandlingshåndterer adskiller sig ikke meget fra nogen anden handling, men den vil blive påberåbt, hvis en undtagelse kastes når som helst under evalueringen af ​​statens handlinger:

@Bean offentlig handling errorAction () {return ctx -> System.out.println ("Fejl" + ctx.getSource (). GetId () + ctx.getException ()); }

Det er også muligt at registrere individuelle handlinger til indgang, gør og Afslut tilstandsovergange:

@Bean offentlig handling 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 blive udført på de tilsvarende tilstandsovergange. For eksempel vil vi måske kontrollere nogle forudgående betingelser på tidspunktet for indrejsen eller udløse nogle rapporter på tidspunktet for udgangen.

5. Globale lyttere

Globale begivenhedslyttere kan defineres til statsmaskinen. Disse lyttere vil blive påberåbt hver gang en tilstandsovergang finder sted og kan bruges til ting som logning eller sikkerhed.

Først skal vi tilføje en anden konfigurationsmetode - en, der ikke beskæftiger sig med tilstande eller overgange, men med konfigurationen til selve tilstandsmaskinen.

Vi er nødt til at definere en lytter ved at udvide StateMachineListenerAdapter:

public class StateMachineListener udvider StateMachineListenerAdapter {@Override public void stateChanged (State from, State to) {System.out.printf ("Transitioned from% s to% s% n", from == null? "none": from.getId ( ), to.getId ()); }}

Her overstyrer vi kun stateChanged selvom mange andre lige kroge er tilgængelige.

6. Udvidet stat

Spring State Machine holder styr på sin tilstand, men for at holde styr på vores Ansøgning tilstand, det være sig nogle beregnede værdier, indgange fra administratorer eller svar fra opkald til eksterne systemer, skal vi bruge det, der kaldes en udvidet tilstand.

Antag, at vi vil sikre os, at en kontoansøgning gennemgår to godkendelsesniveauer. Vi kan holde styr på godkendelsesantal ved hjælp af et heltal, der er gemt i udvidet tilstand:

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

7. Vagter

En vagt kan bruges til at validere nogle data, før en overgang til en tilstand udføres. En vagt ligner meget en handling:

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

Den mærkbare forskel her er, at en vagt returnerer en rigtigt eller falsk som vil informere statsmaskinen om overgangen skal have lov til at finde sted.

Der findes også støtte til SPeL-udtryk som vagter. Eksemplet ovenfor kunne også have været skrevet som:

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

8. Angiv maskin fra en bygherre

StateMachineBuilder kan bruges til at oprette en tilstandsmaskine uden at bruge Spring-annoteringer eller oprette en Spring-kontekst:

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

9. Hierarkiske stater

Hierarkiske tilstande kan konfigureres ved hjælp af flere withStates () i forbindelse med forælder ():

angiver .withStates () .initial ("SI") .state ("SI"). end ("SF"). og () .withStates () .parent ("SI"). initial ("SUB1") .state ("SUB2") .end ("SUBEND");

Denne form for opsætning giver statsmaskinen mulighed for at have flere tilstande, så et opkald til getState () producerer flere ID'er. For eksempel resulterer følgende udtryk umiddelbart efter opstart i:

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

10. Kryds (valg)

Indtil videre har vi skabt statsovergange, som var lineære af natur. Ikke kun er dette temmelig uinteressant, men det afspejler heller ikke virkelige brugssager, som en udvikler bliver bedt om at implementere. Oddsene er, at betingede stier skal implementeres, og Spring State-maskinens kryds (eller valg) tillader os at gøre netop det.

Først skal vi markere en tilstand som et knudepunkt (valg) i tilstandsdefinitionen:

angiver .withStates () .junction ("SJ")

Derefter definerer vi i overgangene først / derefter / sidste muligheder, der svarer til en if-then-else-struktur:

.withJunction () .source ("SJ"). første ("high", highGuard ()). derefter ("medium", mediumGuard ()) .last ("low")

først og derefter tag et andet argument, som er en regelmæssig vagt, der vil blive påberåbt for at finde ud af, hvilken vej du skal tage:

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

Bemærk, at en overgang ikke stopper ved et knudepunkt, men straks udfører definerede vagter og går til en af ​​de udpegede ruter.

I eksemplet ovenfor vil instruktion af tilstandsmaskine om at overgå til SJ resultere i, at den aktuelle tilstand bliver lav da begge vagter bare vender tilbage falske.

En sidste bemærkning er, at API'en giver både kryds og valg. Men funktionelt er de identiske i alle aspekter.

11. Gaffel

Nogle gange bliver det nødvendigt at opdele udførelsen i flere uafhængige udførelsesstier. Dette kan opnås ved hjælp af gaffel funktionalitet.

Først skal vi udpege en knude som en gaffelknude og oprette hierarkiske regioner, hvori statsmaskinen udfører split:

hedder .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 ");

Definér derefter gaffelovergang:

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

12. Deltag

Komplementet med gaffeloperationen er sammenføjningen. Det giver os mulighed for at indstille en tilstandsovergang, som afhænger af at udfylde nogle andre tilstande:

Som med forking er vi nødt til at udpege en sammenføjningsnode i tilstandsdefinitionen:

hedder .withStates () .join ("SJoin")

Derefter definerer vi i overgange, hvilke stater der skal fuldføres for at muliggøre vores tilslutningstilstand:

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

Det er det! Med denne konfiguration, når begge Sub1-2 og Sub2-2 er nået, vil statsmaskinen overgå til S Tilmeld dig

13. Enums I stedet for Strenge

I eksemplerne ovenfor har vi brugt strengkonstanter til at definere tilstande og begivenheder for klarhed og enkelhed. På et reelt verdensproduktionssystem vil man sandsynligvis bruge Java's enums for at undgå stavefejl og få mere typesikkerhed.

Først skal vi definere alle mulige tilstande og begivenheder i vores system:

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

Vi har også brug for at videregive vores enums som generiske parametre, når vi udvider konfigurationen:

offentlig klasse SimpleEnumStateMachineConfiguration udvider StateMachineConfigurerAdapter 

Når vi er defineret, kan vi bruge vores enum-konstanter i stedet for strenge. For eksempel for at definere en overgang:

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

14. Konklusion

Denne artikel udforskede nogle af funktionerne i Spring State Machine.

Som altid kan du finde eksemplet på kildekoden på GitHub.