Vodič kroz projekt državnog stroja Spring

1. Uvod

Ovaj je članak usredotočen na projekt državnog stroja Spring - koji se može koristiti za predstavljanje tijekova posla ili bilo koje druge vrste problema s automatskim predstavljanjem konačnih stanja.

2. Ovisnost Mavena

Za početak moramo dodati glavnu ovisnost o Mavenu:

 org.springframework.statemachine opruga-statemachine-core 1.2.3.Opusti 

Najnoviju verziju ove ovisnosti možete pronaći ovdje.

3. Konfiguracija državnog stroja

Sada, krenimo s definiranjem jednostavnog državnog stroja:

@Configuration @EnableStateMachine javna klasa SimpleStateMachineConfiguration proširuje StateMachineConfigurerAdapter {@Override javna void konfiguracija (StateMachineStateConfigurer države) baca izuzetak {navodi .withStates () .initial ("SI"). ("S1", "S2", "S3"))); } @Override javna void konfiguracija (StateMachineTransitionConfigurer prijelazi) baca iznimku {transitions.withExternal () .source ("SI"). Target ("S1"). Event ("E1"). And () .withExternal () .source ( "S1"). Target ("S2"). Event ("E2"). And () .withExternal () .source ("S2"). Target ("SF"). Event ("end"); }}

Imajte na umu da je ova klasa označena kao konvencionalna konfiguracija opruge kao i državni stroj. Također se treba produžiti StateMachineConfigurerAdapter tako da se mogu pozivati ​​razne metode inicijalizacije. U jednoj od metoda konfiguracije definiramo sva moguća stanja stanja stroja, u drugoj, kako događaji mijenjaju trenutno stanje.

Gornja konfiguracija prikazuje prilično jednostavan, linearni stroj za prijelaz koji bi trebao biti dovoljno jednostavan za praćenje.

Sada moramo pokrenuti kontekst Spring i dobiti referencu na državni stroj definiran našom konfiguracijom:

@Autowired private StateMachine stateMachine;

Nakon što imamo državni stroj, treba ga pokrenuti:

stateMachine.start ();

Sada kada je naš stroj u početnom stanju, možemo slati događaje i tako pokretati prijelaze:

stateMachine.sendEvent ("E1");

Uvijek možemo provjeriti trenutno stanje državnog stroja:

stateMachine.getState ();

4. Akcije

Dodajmo neke radnje koje će se izvršiti oko prijelaza stanja. Prvo definiramo svoju akciju kao Spring bean u istoj konfiguracijskoj datoteci:

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

Tada možemo registrirati gore stvorenu akciju na prijelazu u našoj konfiguracijskoj klasi:

@Override javna void konfiguracija (StateMachineTransitionConfigurer prijelazi) baca iznimku {transitions.withExternal () transitions.withExternal () .source ("SI"). Target ("S1") .event ("E1"). Action (initAction ())

Ova će se radnja izvršiti prilikom prijelaza iz SI do S1 putem događaja E1 javlja se. Akcije se mogu priložiti samim državama:

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

Ova funkcija definicije stanja prihvaća operaciju koja se izvršava kada je stroj u ciljnom stanju i, opcionalno, obrađivač radnje pogreške.

Obrađivač radnje pogreške ne razlikuje se puno od bilo koje druge radnje, ali bit će pozvan ako se u bilo kojem trenutku tijekom procjene državnih radnji izuzme izuzetak:

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

Također je moguće registrirati pojedinačne radnje za ulazak, čini i Izlaz državni prijelazi:

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

Odgovarajuće radnje izvršit će se na odgovarajućim prijelazima stanja. Na primjer, možda ćemo htjeti provjeriti neke preduvjete u trenutku ulaska ili pokrenuti izvješćivanje u trenutku izlaska.

5. Globalni slušatelji

Slušatelji globalnih događaja mogu se definirati za državni stroj. Ti će se slušatelji pozivati ​​svaki put kad se dogodi prijelaz stanja i mogu se koristiti za stvari kao što su bilježenje ili sigurnost.

Prvo, moramo dodati drugu metodu konfiguracije - onu koja se ne bavi stanjima ili prijelazima, već konfiguracijom samog državnog stroja.

Moramo definirati slušatelja širenjem StateMachineListenerAdapter:

javna klasa StateMachineListener proširuje StateMachineListenerAdapter {@Override public void stateChanged (stanje iz, stanje u) {System.out.printf ("Premješteno iz% s u% s% n", iz == null? "none": from.getId ( ), to.getId ()); }}

Ovdje smo samo poništili stanjePromijenjeno iako su dostupne mnoge druge čak udice.

6. Proširena država

Spring State Machine prati svoje stanje, ali da bi pratio i naše primjena države, bilo da se radi o nekim izračunatim vrijednostima, unosima administratora ili odgovorima pozivanja vanjskih sustava, trebamo koristiti ono što se naziva proširena država.

Pretpostavimo da želimo biti sigurni da aplikacija za račun prolazi kroz dvije razine odobrenja. Možemo pratiti broj odobrenja pomoću cijelog broja pohranjenog u proširenom stanju:

@Bean javna akcija executeAction () {return ctx -> {int odobrenja = (int) ctx.getExtendedState (). GetVariables () .getOrDefault ("odobrenjeCount", 0); odobrenja ++; ctx.getExtendedState (). getVariables () .put ("odobrenjeCount", odobrenja); }; }

7. Stražari

Zaštita se može koristiti za provjeru valjanosti nekih podataka prije izvršenja prijelaza u stanje. Stražar izgleda vrlo slično akciji:

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

Ovdje je primjetna razlika u tome što stražar vraća a pravi ili lažno koji će obavijestiti državni stroj treba li dopustiti da se dogodi prijelaz.

Podrška za SPeL izraze kao zaštitnike također postoji. Gornji primjer također je mogao biti napisan kao:

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

8. Državni stroj od graditelja

StateMachineBuilder može se koristiti za stvaranje državnog stroja bez upotrebe proljetnih napomena ili stvaranja proljetnog konteksta:

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 stroj = builder.build ();

9. Hijerarhijska stanja

Hijerarhijska stanja mogu se konfigurirati pomoću višestrukih withStates () u spoju sa roditelj():

navodi .withStates () .initial ("SI") .state ("SI") .end ("SF") .and () .withStates () .parent ("SI") .initial ("SUB1") .state ("SUB2") .end ("SUBEND");

Ovakva postavka omogućuje državnom stroju da ima više stanja, pa poziv na getState () proizvest će više osobnih iskaznica. Na primjer, odmah nakon pokretanja sljedeći izraz rezultira:

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

10. Spojevi (izbori)

Do sada smo stvorili prijelaze stanja koji su po prirodi bili linearni. Ne samo da je ovo prilično nezanimljivo, već također ne odražava slučajeve stvarne upotrebe za koje će se od programera tražiti da ih primijeni. Izgledi da će biti potrebni uvjetovani putovi, a spojevi (ili izbori) stroja Spring State nam omogućuju upravo to.

Prvo, moramo u definiciji države označiti spoj (izbor) države:

navodi .withStates () .junction ("SJ")

Zatim u prijelazima definiramo prvu / tada / zadnju opciju koja odgovara strukturi ako-tada-drugo:

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

prvi i zatim uzmi drugi argument koji je redoviti čuvar koji će se pozvati kako bi se saznalo kojim putem krenuti:

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

Imajte na umu da se prijelaz ne zaustavlja na čvoru čvora, već će odmah izvršiti definirane zaštitnike i preći na jednu od naznačenih ruta.

U gornjem primjeru, upućivanje državnog stroja za prijelaz na SJ rezultirat će stvarnim stanjem niska jer se obojica čuvara samo vraćaju lažno.

Posljednja napomena je to API pruža i spojeve i izbore. Međutim, funkcionalno su identični u svakom aspektu.

11. Vilica

Ponekad je potrebno izvršenje podijeliti na više neovisnih putova izvršenja. To se može postići pomoću vilica funkcionalnost.

Prvo, moramo odrediti čvor kao čvor rašlje i stvoriti hijerarhijska područja u koja će državni stroj izvršiti podjelu:

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

Zatim definirajte prijelaz vilice:

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

12. Pridružite se

Dopuna operacije račvanja je spajanje. Omogućuje nam postavljanje stanja prijelaza u koje ovisi o dovršavanju nekih drugih stanja:

Kao i kod račvanja, i u definiciji stanja moramo odrediti čvor za pridruživanje:

navodi .withStates () .join ("SJoin")

Zatim u prijelazima definiramo koja stanja treba dovršiti da bismo omogućili naše stanje pridruživanja:

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

To je to! S ovom konfiguracijom, kada oboje Sub1-2 i Sub2-2 ako se postignu, državni stroj će prijeći na PRIDRUŽITE SE

13. Enum Umjesto Žice

U gornjim primjerima koristili smo niz konstanti za definiranje stanja i događaja radi jasnoće i jednostavnosti. U stvarnom proizvodnom sustavu vjerojatno bi netko želio koristiti Java-ove enume kako bi izbjegao pravopisne pogreške i stekao veću sigurnost tipa.

Prvo, moramo definirati sva moguća stanja i događaje u našem sustavu:

javni popis ApplicationReviewStates {PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED} javni popis ApplicationReviewEvents {APPROVE, REJECT}

Također moramo proslijediti naše enume kao generičke parametre kada proširimo konfiguraciju:

javna klasa SimpleEnumStateMachineConfiguration proširuje StateMachineConfigurerAdapter 

Jednom definirane, možemo koristiti naše enum konstante umjesto nizova. Na primjer za definiranje prijelaza:

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

14. Zaključak

Ovaj je članak istraživao neke značajke proljetnog državnog stroja.

Kao i uvijek, možete pronaći uzorak izvornog koda na GitHubu.