From c26631d92062542deb38e1df13764cb47c61908a Mon Sep 17 00:00:00 2001 From: ZhangKai Date: Mon, 7 Mar 2022 22:19:43 +0800 Subject: [PATCH] #31 Spring statemachine --- .../spring-statemachine.md | 420 +++++++++--------- .../spring-statemachine.md | 416 ++++++++--------- 2 files changed, 418 insertions(+), 418 deletions(-) diff --git a/docs/en/spring-statemachine/spring-statemachine.md b/docs/en/spring-statemachine/spring-statemachine.md index ec25e36..1a3ef74 100644 --- a/docs/en/spring-statemachine/spring-statemachine.md +++ b/docs/en/spring-statemachine/spring-statemachine.md @@ -1,6 +1,6 @@ # Spring Statemachine - Reference Documentation -## [](#preface)Preface +## Preface The concept of a state machine is most likely older than any reader of this reference documentation and definitely older than the Java @@ -25,7 +25,7 @@ This reference documentation contains the following parts: [Appendices](#appendices) contains generic information about used material and state machines. -# [](#introduction)Introduction +# Introduction Spring Statemachine (SSM) is a framework that lets application developers use traditional state machine concepts with Spring applications. SSM @@ -51,7 +51,7 @@ Before you continue, we recommend going through the appendices [Glossary](#gloss The rest of the documentation expects you to be familiar with state machine concepts. -## [](#background)Background +## Background State machines are powerful because their behavior is always guaranteed to be consistent and relatively easily debugged due to how operational @@ -73,7 +73,7 @@ full of spaghetti. Spaghetti code looks like a never ending, hierarchical structure of IF, ELSE, and BREAK clauses, and compilers should probably ask developers to go home when things are starting to look too complex. -## [](#usage-scenarios)Usage Scenarios +## Usage Scenarios A project is a good candidate to use a state machine when: @@ -96,7 +96,7 @@ You are already trying to implement a state machine when you: enum is set, and then make further exceptions about what to do when certain combinations of your flags and enums exist or do not exist. -# [](#statemachine-getting-started)Getting started +# Getting started If you are just getting started with Spring Statemachine, this is the section for you! Here, we answer the basic @@ -105,7 +105,7 @@ introduction to Spring Statemachine. We then build our first Spring Statemachine application and discuss some core principles as we go. -## [](#system-requirement)System Requirement +## System Requirement Spring Statemachine 3.0.1 is built and tested with JDK 8 (all artifacts have JDK 7 compatibility) and Spring @@ -118,7 +118,7 @@ on `spring-shell` and `spring-boot`, which pull other dependencies beyond the framework itself. Also, the optional security and data access features have dependencies to on Spring Security and Spring Data modules. -## [](#modules)Modules +## Modules The following table describes the modules that are available for Spring Statemachine. @@ -139,7 +139,7 @@ The following table describes the modules that are available for Spring Statemac | `spring-statemachine-bom` | Bill of Materials pom. | | `spring-statemachine-starter` | Spring Boot starter. | -## [](#using-gradle)Using Gradle +## Using Gradle The following listing shows a typical `build.gradle` file created by choosing various settings at [https://start.spring.io](https://start.spring.io): @@ -203,7 +203,7 @@ The expected Spring Boot-packaged fat jar would be `build/libs/demo-0.0.1-SNAPSH | |You do not need the`libs-milestone` and `libs-snapshot` repositories for
production development.| |---|----------------------------------------------------------------------------------------------------| -## [](#using-maven)Using Maven +## Using Maven The following example shows a typical `pom.xml` file, which was created by choosing various options at [https://start.spring.io](https://start.spring.io): @@ -324,7 +324,7 @@ The expected Spring Boot-packaged fat-jar would be `target/demo-0.0.1-SNAPSHOT.j | |You do not need the `libs-milestone` and `libs-snapshot` repositories for
production development.| |---|-----------------------------------------------------------------------------------------------------| -## [](#developing-your-first-spring-statemachine-application)Developing Your First Spring Statemachine Application +## Developing Your First Spring Statemachine Application You can start by creating a simple Spring Boot `Application` class that implements `CommandLineRunner`. The following example shows how to do so: @@ -430,9 +430,9 @@ State change to S2 These lines indicate that the machine you constructed is moving from one state to another, as it should. -# [](#whatsnew)What’s New +# What’s New -## [](#in-1-1)In 1.1 +## In 1.1 Spring Statemachine 1.1 focuses on security and better interoperability with web applications. It includes the following: @@ -464,7 +464,7 @@ interoperability with web applications. It includes the following: * UI modeling support using Eclipse Papyrus. See [Eclipse Modeling Support](#sm-papyrus). -## [](#in-1-2)In 1.2 +## In 1.2 Spring Statemachine 1.2 focuses on generic enhancements, better UML support, and integrations with external config repositories. @@ -485,7 +485,7 @@ It includes the following: * Support for tracing and monitoring. See [Monitoring a State Machine](#sm-monitoring). -### [](#in-1-2-8)In 1.2.8 +### In 1.2.8 Spring Statemachine 1.2.8 contains a bit more functionality than normally not seen in a point release, but these changes did not merit a fork of @@ -499,11 +499,11 @@ Spring Statemachine 1.3. It includes the following: * Transition conflict policy. See[Configuring Common Settings](#statemachine-config-commonsettings) -## [](#in-2-0)In 2.0 +## In 2.0 Spring Statemachine 2.0 focuses on Spring Boot 2.x support. -### [](#in-2-0-0)In 2.0.0 +### In 2.0.0 Spring Statemachine 2.0.0 includes the following: @@ -511,7 +511,7 @@ Spring Statemachine 2.0.0 includes the following: * The `spring-statemachine-boot` module has been renamed to `spring-statemachine-autoconfigure`. -## [](#in-3-0)In 3.0 +## In 3.0 Spring Statemachine 3.0.0 focuses on adding a Reactive support. Moving from `2.x` to `3.x` is introducing some breaking changes which are detailed in [Reactor Migration Guide](#appendix-reactormigrationguide). @@ -525,7 +525,7 @@ in a future releases. At this point most of a documentation has been changed to showcase reactive interfaces while we still keep some notes around to users still using old blocking methods. -# [](#statemachine)Using Spring Statemachine +# Using Spring Statemachine This part of the reference documentation explains the core functionality that Spring Statemachine provides to any Spring based application. @@ -580,7 +580,7 @@ It includes the following topics: * [Repository Support](#sm-repository) describes the state machine repository config support. -## [](#sm-config)Statemachine Configuration +## Statemachine Configuration One of the common tasks when using a state machine is to design its runtime configuration. This chapter focuses on how Spring @@ -591,7 +591,7 @@ manageable. | |Configuration examples in this section are not feature complete. That is,
you always need to have definitions of both states and transitions.
Otherwise, state machine configuration would be ill-formed. We have
simply made code snippets less verbose by leaving other needed parts
out.| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#statemachine-config-annotations)Using `enable` Annotations +### Using `enable` Annotations We use two familiar Spring *enabler* annotations to ease configuration:`@EnableStateMachine` and `@EnableStateMachineFactory`. These annotations, when placed in a `@Configuration` class, enable @@ -610,7 +610,7 @@ instance of a `StateMachineFactory`. | |Usage examples of these are shown in below sections.| |---|----------------------------------------------------| -### [](#statemachine-config-states)Configuring States +### Configuring States We get into more complex configuration examples a bit later in this guide, but we first start with something simple. For most simple state @@ -663,7 +663,7 @@ public class Config1Strings | |Using enumerations brings a safer set of states and event types but
limits possible combinations to compile time. Strings do not have this
limitation and let you use more dynamic ways to build state
machine configurations but do not allow same level of safety.| |---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#configuring-hierarchical-states)Configuring Hierarchical States +### Configuring Hierarchical States You can define hierarchical states can by using multiple `withStates()`calls, where you can use `parent()` to indicate that these particular states are sub-states of some other state. @@ -692,7 +692,7 @@ public class Config2 } ``` -### [](#configuring-regions)Configuring Regions +### Configuring Regions There are no special configuration methods to mark a collection of states to be part of an orthogonal state. To put it simply, orthogonal @@ -770,7 +770,7 @@ public class Config10RegionId } ``` -### [](#configuring-transitions)Configuring Transitions +### Configuring Transitions We support three different types of transitions: `external`,`internal`, and `local`. Transitions are triggered either by a signal (which is an event sent into a state machine) or by a timer. @@ -811,7 +811,7 @@ public class Config3 } ``` -### [](#configuring-guards)Configuring Guards +### Configuring Guards You can use guards to protect state transitions. You can use the `Guard` interface to do an evaluation where a method has access to a `StateContext`. @@ -863,7 +863,7 @@ expression-based guard is a `SpelExpressionGuard`. We attached it to the transition between states `S2` and `S3`. Both guards always evaluate to `true`. -### [](#statemachine-config-actions)Configuring Actions +### Configuring Actions You can define actions to be executed with transitions and states. An action is always run as a result of a transition that @@ -958,7 +958,7 @@ with states `S1`, `S2`, and `S3`. We need to clarify what is going on here: | |Defining action with `initial()` function only runs a particular
action when a state machine or sub state is started. This action
is an initializing action that is run only once. An action defined
with `state()` is then run if the state machine transitions back
and forward between initial and non-initial states.| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -#### [](#state-actions)State Actions +#### State Actions State actions are run differently compared to entry and exit actions, because execution happens after state has been entered @@ -1044,7 +1044,7 @@ void sendEventUsingTimeout() { } ``` -#### [](#statemachine-config-transition-actions-errorhandling)Transition Action Error Handling +#### Transition Action Error Handling You can always catch exceptions manually. However, with actions defined for transitions, you can define an error action that is called if an @@ -1111,7 +1111,7 @@ public void configure(StateMachineTransitionConfigurer transitio } ``` -#### [](#statemachine-config-state-actions-errorhandling)State Action Error Handling +#### State Action Error Handling Logic similar to the logic that handles errors in state transitions is also available for entry to a state and exit from a state. @@ -1163,13 +1163,13 @@ public class Config55 } ``` -### [](#configuring-pseudo-states)Configuring Pseudo States +### Configuring Pseudo States *Pseudo state* configuration is usually done by configuring states and transitions. Pseudo states are automatically added to state machine as states. -#### [](#initial-state)Initial State +#### Initial State You can mark a particular state as initial state by using the `initial()`method. This initial action is good, for example, to initialize extended state variables. The following example shows how to use the `initial()` method: @@ -1204,7 +1204,7 @@ public class Config11 } ``` -#### [](#terminate-state)Terminate State +#### Terminate State You can mark a particular state as being an end state by using the `end()` method. You can do so at most once for each individual sub-machine or region. @@ -1229,7 +1229,7 @@ public class Config1Enums } ``` -#### [](#state-history)State History +#### State History You can define state history once for each individual state machine. You need to choose its state identifier and set either `History.SHALLOW` or`History.DEEP`. The following example uses `History.SHALLOW`: @@ -1276,7 +1276,7 @@ state transition is not defined, then normal entry into a region is done. This default transition is also used if a machine’s history is a final state. -#### [](#choice-state)Choice State +#### Choice State Choice needs to be defined in both states and transitions to work properly. You can mark a particular state as being a choice state by using the `choice()`method. This state needs to match source state when a transition is @@ -1394,7 +1394,7 @@ public class Config23 | |Junction have same api format meaning actions can be defined
similarly.| |---|---------------------------------------------------------------------------| -#### [](#statemachine-config-states-junction)Junction State +#### Junction State You need to define a junction in both states and transitions for it to work properly. You can mark a particular state as being a choice state by using the `junction()`method. This state needs to match the source state when a transition is @@ -1464,7 +1464,7 @@ public class Config20 | |The difference between choice and junction is purely academic, as both are
implemented with `first/then/last` structures . However, in theory, based
on UML modeling, `choice` allows only one incoming transition while`junction` allows multiple incoming transitions. At a code level, the
functionality is pretty much identical.| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -#### [](#fork-state)Fork State +#### Fork State You must define a fork in both states and transitions for it to work properly. You can mark a particular state as being a choice state by using the `fork()`method. This state needs to match source state when a transition is @@ -1518,7 +1518,7 @@ public class Config14 } ``` -#### [](#join-state)Join State +#### Join State You must define a join in both states and transitions for it to work properly. You can mark aparticular state as being a choice state by using the `join()`method. This state does not need to match either source states or a @@ -1636,7 +1636,7 @@ public class Config22 } ``` -#### [](#statemachine-config-states-exitentry)Exit and Entry Point States +#### Exit and Entry Point States You can use exit and entry points to do more controlled exit and entry from and into a submachine. @@ -1693,7 +1693,7 @@ As shown in the preceding, you need to mark particular states as being `exit` an and also specify `withExit()` and `withEntry()`, where those states exit and entry respectively. -### [](#statemachine-config-commonsettings)Configuring Common Settings +### Configuring Common Settings You can set part of a common state machine configuration by using`ConfigurationConfigurer`. With it you can set `BeanFactory` and an autostart flag for a state machine. It also lets you register `StateMachineListener` instances, @@ -1816,7 +1816,7 @@ For more about config model, see [StateMachine Config Model](#devdocs-configmode | |The `withSecurity`, `withMonitoring` and `withPersistence` configuration methods
are documented in [State Machine Security](#sm-security), [Monitoring a State Machine](#sm-monitoring), and[Using `StateMachineRuntimePersister`](#sm-persist-statemachineruntimepersister), respectively.| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#configuring-model)Configuring Model +### Configuring Model `StateMachineModelFactory` is a hook that lets you configure a statemachine model without using a manual configuration. Essentially, it is a third-party @@ -1878,7 +1878,7 @@ public static class CustomStateMachineModelFactory implements StateMachineModelF You can find an example of using this model factory integration in[Eclipse Modeling Support](#sm-papyrus). You can find more generic info about custom model integration in [Developer Documentation](#devdocs). -### [](#statemachine-config-thingstoremember)Things to Remember +### Things to Remember When defining actions, guards, or any other references from a configuration, it pays to remember how Spring Framework works @@ -1950,7 +1950,7 @@ public class Config1 } ``` -## [](#sm-machineid)State Machine ID +## State Machine ID Various classes and interfaces use `machineId` either as a variable or as a parameter in methods. This section takes a closer look at how`machineId` relates to normal machine operation and instantiation. @@ -1961,7 +1961,7 @@ following logs or doing deeper debugging. Having a lot of different machine instances quickly gets developers lost in translation if there is no easy way to identify these instances. As a result, we added the option to set the`machineId`. -### [](#using-enablestatemachine)Using `@EnableStateMachine` +### Using `@EnableStateMachine` Setting `machineId` in Java configuration as `mymachine` then exposes that value for logs. This same `machineId` is also available from the`StateMachine.getId()` method. The following example uses the `machineId` method: @@ -1986,7 +1986,7 @@ started S2 S1 / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine | |The manual builder (see [State Machine through a Builder](#state-machine-via-builder)) uses the same configuration
interface, meaning that the behavior is equivalent.| |---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#using-enablestatemachinefactory)Using `@EnableStateMachineFactory` +### Using `@EnableStateMachineFactory` You can see the same `machineId` getting configured if you use a`StateMachineFactory` and request a new machine by using that ID, as the following example shows: @@ -1996,7 +1996,7 @@ StateMachineFactory factory = context.getBean(StateMachineFactor StateMachine machine = factory.getStateMachine("mymachine"); ``` -### [](#using-statemachinemodelfactory)Using `StateMachineModelFactory` +### Using `StateMachineModelFactory` Behind the scenes, all machine configurations are first translated into a`StateMachineModel` so that `StateMachineFactory` need not know from where the configuration originated, as a machine can be built from @@ -2015,7 +2015,7 @@ without a known machine id. | |Currently, `UmlStateMachineModelFactory` does not distinguish between
different machine IDs, as UML source is always coming from the same
file. This may change in future releases.| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-factories)State Machine Factories +## State Machine Factories There are use cases when a state machine needs to be created dynamically instead of by defining static configuration at compile time. For example, @@ -2028,7 +2028,7 @@ Configuration for a state machine factory is exactly the same as shown in various examples in this document where state machine configuration is hard coded. -### [](#factory-through-an-adapter)Factory through an Adapter +### Factory through an Adapter Actually creating a state machine by using `@EnableStateMachine`works through a factory, so `@EnableStateMachineFactory` merely exposes that factory through its interface. The following example uses`@EnableStateMachineFactory`: @@ -2069,7 +2069,7 @@ public class Bean3 { } ``` -#### [](#adapter-factory-limitations)Adapter Factory Limitations +#### Adapter Factory Limitations The current limitation of factory is that all the actions and guard with which it associates a state machine share the same instance. @@ -2078,7 +2078,7 @@ specifically handle the case in which the same bean is called by different state machines. This limitation is something that will be resolved in future releases. -### [](#state-machine-via-builder)State Machine through a Builder +### State Machine through a Builder Using adapters (as shown above) has a limitation imposed by its requirement to work through Spring `@Configuration` classes and the @@ -2131,7 +2131,7 @@ returned from a `withConfiguration()` to setup `autoStart` and `BeanFactory`. You can also use one to register a `StateMachineListener`. If a `StateMachine`instance returned from a builder is registered as a bean by using `@Bean`, `BeanFactory`is attached automatically. If you use instances outside of a spring application context, you must use these methods to set up the needed facilities. -## [](#sm-deferevents)Using Deferred Events +## Using Deferred Events When an event is sent, it may fire an `EventTrigger`, which may then cause a transition to happen, if a state machine is in a state where a trigger is @@ -2254,7 +2254,7 @@ sub-state that would then override the anonymous transition between the `DEPLOY` and `DONE` states if the state machine happens to be in a`DEPLOYPREPARE` state when the `DONE` event is dispatched. In the`DEPLOYEXECUTE` state when the `DONE` event is not deferred, this event would be handled in a super state. -## [](#sm-scopes)Using Scopes +## Using Scopes Support for scopes in a state machine is very limited, but you can enable `session` scope by using a normal Spring `@Scope` annotation in one of two ways: @@ -2365,7 +2365,7 @@ public class StateMachineController { | |Spring Statemachine poms have no dependencies to Spring MVC
classes, which you will need to work with session scope. However, if you are
working with a web application, you have already pulled those dependencies
directly from Spring MVC or Spring Boot.| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-actions)Using Actions +## Using Actions Actions are one of the most useful components that you can use to interact and collaborate with a state machine. You can run actions @@ -2437,12 +2437,12 @@ a state machine. | |`StateContext` is described in [Using `StateContext`](#sm-statecontext).| |---|------------------------------------------------------------------------| -### [](#spel-expressions-with-actions)SpEL Expressions with Actions +### SpEL Expressions with Actions You can also use a SpEL expression as a replacement for a full `Action` implementation. -### [](#sm-actions-reactive)Reactive Actions +### Reactive Actions Normal `Action` interface is a simple functional method taking `StateContext`and returning *void*. There’s nothing blocking here until you block in a method itself and this is a bit of a problem as framework cannot @@ -2467,7 +2467,7 @@ public interface ReactiveAction extends Function, Mono< | |Internally old `Action` interface is wrapped with a Reactor Mono Runnable as it
shares same return type. We have no control what you do in that method!| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-guards)Using Guards +## Using Guards As shown in [Things to Remember](#statemachine-config-thingstoremember), the `guard1` and `guard2` beans are attached to the entry and exit states, respectively. @@ -2530,7 +2530,7 @@ public class BaseGuard implements Guard { | |`StateContext` is described in section [Using `StateContext`](#sm-statecontext).| |---|--------------------------------------------------------------------------------| -### [](#spel-expressions-with-guards)SpEL Expressions with Guards +### SpEL Expressions with Guards You can also use a SpEL expression as a replacement for a full Guard implementation. The only requirement is that the expression needs @@ -2538,7 +2538,7 @@ to return a `Boolean` value to satisfy the `Guard` implementation. This can be demonstrated with a `guardExpression()` function that takes an expression as an argument. -### [](#sm-guards-reactive)Reactive Guards +### Reactive Guards Normal `Guard` interface is a simple functional method taking `StateContext`and returning *boolean*. There’s nothing blocking here until you block in a method itself and this is a bit of a problem as framework cannot @@ -2563,7 +2563,7 @@ public interface ReactiveGuard extends Function, Monocontrol what you do in that method!| |---|----------------------------------------------------------------------------------------------------------------------------| -## [](#sm-extendedstate)Using Extended State +## Using Extended State Assume that you need to create a state machine that tracks how many times a user is pressing a key on a keyboard and then terminates @@ -2625,7 +2625,7 @@ public class ExtendedStateVariableEventListener } ``` -## [](#sm-statecontext)Using `StateContext` +## Using `StateContext` [`StateContext`](https://docs.spring.io/spring-statemachine/docs/3.0.1/api/org/springframework/statemachine/StateContext.html) is one of the most important objects when working with a state machine, as it is passed into various methods @@ -2657,19 +2657,19 @@ You can use `StateContext` to get access to the following: `StateContext` is passed into various components, such as`Action` and `Guard`. -### [](#sm-statecontext-stage)Stages +### Stages [`Stage`](https://docs.spring.io/spring-statemachine/docs/3.0.1/api/org/springframework/statemachine/StateContext.Stage.html) is arepresentation of a `stage` on which a state machine is currently interacting with a user. The currently available stages are `EVENT_NOT_ACCEPTED`, `EXTENDED_STATE_CHANGED`,`STATE_CHANGED`, `STATE_ENTRY`, `STATE_EXIT`, `STATEMACHINE_ERROR`,`STATEMACHINE_START`, `STATEMACHINE_STOP`, `TRANSITION`,`TRANSITION_START`, and `TRANSITION_END`. These states may look familiar, as they match how you can interact with listeners (as described in[Listening to State Machine Events](#sm-listeners)). -## [](#sm-triggers)Triggering Transitions +## Triggering Transitions Driving a state machine is done by using transitions, which are triggered by triggers. The currently supported triggers are `EventTrigger` and`TimerTrigger`. -### [](#using-eventtrigger)Using `EventTrigger` +### Using `EventTrigger` `EventTrigger` is the most useful trigger, because it lets you directly interact with a state machine by sending events to it. These @@ -2741,14 +2741,14 @@ Flux> results = results.subscribe(); ``` -#### [](#sm-triggers-statemachineeventresult)StateMachineEventResult +#### StateMachineEventResult `StateMachineEventResult` contains more detailed information about a result of a event sending. From this you can get a `Region` which handled an event,`Message` itself and what was an actual `ResultType`. From `ResultType` you can see if message was accepted, denied or deferred. Generally speaking when subscribtion completes, events are passed into a machine. -### [](#using-timertrigger)Using `TimerTrigger` +### Using `TimerTrigger` `TimerTrigger` is useful when something needs to be triggered automatically without any user interaction. `Trigger` is added to a @@ -2833,7 +2833,7 @@ after the state is entered (after a delay defined in a timer). | |Use `timerOnce()` if you want something to happen after a delay
exactly once when state is entered.| |---|-------------------------------------------------------------------------------------------------------| -## [](#sm-listeners)Listening to State Machine Events +## Listening to State Machine Events There are use cases where you want to know what is happening with a state machine, react to something, or get logging details for @@ -2847,7 +2847,7 @@ these basically provide the same information. One produces events as event classes, and the other produces callbacks via a listener interface. Both of these have pros and cons, which we discuss later. -### [](#application-context-events)Application Context Events +### Application Context Events Application context events classes are `OnTransitionStartEvent`,`OnTransitionEvent`, `OnTransitionEndEvent`, `OnStateExitEvent`,`OnStateEntryEvent`, `OnStateChangedEvent`, `OnStateMachineStart`,`OnStateMachineStop`, and others that extend the base event class,`StateMachineEvent`. These can be used as is with a Spring`ApplicationListener`. @@ -2901,7 +2901,7 @@ public class ManualBuilderConfig { } ``` -### [](#using-statemachinelistener)Using `StateMachineListener` +### Using `StateMachineListener` By using `StateMachineListener`, you can either extend it and implement all callback methods or use the `StateMachineListenerAdapter`class, which contains stub method implementations and choose which ones @@ -2989,7 +2989,7 @@ public class Config7 { } ``` -### [](#limitations-and-problems)Limitations and Problems +### Limitations and Problems Spring application context is not the fastest event bus out there, so we advise giving some thought to the rate of events the state machine @@ -3012,7 +3012,7 @@ public class Config9 } ``` -## [](#sm-context)Context Integration +## Context Integration It is a little limited to do interaction with a state machine by either listening to its events or using actions with states and @@ -3084,7 +3084,7 @@ public @interface WithMyBean { | |The return type of these methods does not matter and is effectively
discarded.| |---|----------------------------------------------------------------------------------| -### [](#enabling-integration)Enabling Integration +### Enabling Integration You can enable all the features of `@WithStateMachine` by using the `@EnableWithStateMachine` annotation, which imports the needed @@ -3129,7 +3129,7 @@ static class Bean17 { | |If a machine is not created as a bean, you need to set`BeanFactory` for a machine, as shown in the prededing example. Otherwise, tge machine is
unaware of handlers that call your `@WithStateMachine` methods.| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#method-parameters)Method Parameters +### Method Parameters Every annotation support exactly the same set of possible method parameters, but runtime behavior differs, depending on the @@ -3183,7 +3183,7 @@ public class Bean4 { | |Instead of getting all event headers with `@EventHeaders`, you can use`@EventHeader`, which can bound to a single header.| |---|-------------------------------------------------------------------------------------------------------------------------| -### [](#state-machine-transition-annotations)Transition Annotations +### Transition Annotations The annotations for transitions are `@OnTransition`, `@OnTransitionStart`, and `@OnTransitionEnd`. @@ -3257,7 +3257,7 @@ public class Bean7 { } ``` -### [](#state-annotations)State Annotations +### State Annotations The following annotations for states are available: `@OnStateChanged`, `@OnStateEntry`, and`@OnStateExit`. The following example shows how to use `OnStateChanged` annotation (the other two work the same way): @@ -3325,7 +3325,7 @@ public class Bean11 { } ``` -### [](#event-annotation)Event Annotation +### Event Annotation There is one event-related annotation. It is named `@OnEventNotAccepted`. If you specify the `event` property, you can listen for a specific event not being @@ -3346,7 +3346,7 @@ public class Bean12 { } ``` -### [](#state-machine-annotations)State Machine Annotations +### State Machine Annotations The following annotations are available for a state machine: `@OnStateMachineStart`,`@OnStateMachineStop`, and `@OnStateMachineError`. @@ -3379,7 +3379,7 @@ public class Bean14 { } ``` -### [](#extended-state-annotation)Extended State Annotation +### Extended State Annotation There is one extended state-related annotation. It is named`@OnExtendedStateChanged`. You can also listen to changes only for specific `key` changes. The following example shows how to use the`@OnExtendedStateChanged`, both with and without a `key` property: @@ -3398,7 +3398,7 @@ public class Bean15 { } ``` -## [](#sm-accessor)Using `StateMachineAccessor` +## Using `StateMachineAccessor` `StateMachine` is the main interface for communicating with a state machine. From time to time, you may need to get more dynamic and @@ -3451,7 +3451,7 @@ stateMachine.getStateMachineAccessor() .withRegion().setRelay(stateMachine); ``` -## [](#sm-interceptor)Using `StateMachineInterceptor` +## Using `StateMachineInterceptor` Instead of using a `StateMachineListener` interface, you can use a `StateMachineInterceptor`. One conceptual difference is that you can use an @@ -3512,7 +3512,7 @@ stateMachine.getStateMachineAccessor() | |For more about the error handling shown in preceding example, see[State Machine Error Handling](#sm-error-handling).| |---|--------------------------------------------------------------------------------------------------------------------| -## [](#sm-security)State Machine Security +## State Machine Security Security features are built atop of functionality from[Spring Security](https://projects.spring.io/spring-security). Security features are handy when it is required to protect part of a state machine @@ -3535,7 +3535,7 @@ You can find more detailed information in [Understanding Security](#sm-security- | |For a complete example, see the [Security](#statemachine-examples-security) sample.| |---|-----------------------------------------------------------------------------------| -### [](#configuring-security)Configuring Security +### Configuring Security All generic configurations for security are done in`SecurityConfigurer`, which is obtained from`StateMachineConfigurationConfigurer`. By default, security is disabled, even if Spring Security classes are @@ -3562,7 +3562,7 @@ If you absolutely need to, you can customize `AccessDecisionManager` for both ev transitions. If you do not define decision managers or set them to `null`, default managers are created internally. -### [](#securing-events)Securing Events +### Securing Events Event security is defined on a global level by a `SecurityConfigurer`. The following example shows how to enable event security: @@ -3590,7 +3590,7 @@ expression needs to return either `TRUE` or `FALSE`. We also defined an attribute of `ROLE_ANONYMOUS` and a `ComparisonType` of `ANY`. For more about using attributes and expressions, see [Using Security Attributes and Expressions](#sm-security-attributes-expressions). -### [](#securing-transitions)Securing Transitions +### Securing Transitions You can define transition security globally, as the following example shows. @@ -3635,7 +3635,7 @@ static class Config2 extends StateMachineConfigurerAdapter { For more about using attributes and expressions, see [Using Security Attributes and Expressions](#sm-security-attributes-expressions). -### [](#securing-actions)Securing Actions +### Securing Actions There are no dedicated security definitions for actions in a state machine, but you can secure actions by using a global method security @@ -3709,7 +3709,7 @@ public static class Config5 extends WebSecurityConfigurerAdapter { See the Spring Security reference guide (available[here](https://spring.io/projects/spring-security#learn)) for more detail. -### [](#sm-security-attributes-expressions)Using Security Attributes and Expressions +### Using Security Attributes and Expressions Generally, you can define security properties in either of two ways: by using security attributes and by using security expressions. @@ -3717,7 +3717,7 @@ Attributes are easier to use but are relatively limited in terms of functionality. Expressions provide more features but are a little bit harder to use. -#### [](#generic-attribute-usage)Generic Attribute Usage +#### Generic Attribute Usage By default, `AccessDecisionManager` instances for events and transitions both use a `RoleVoter`, meaning you can use role attributes @@ -3728,7 +3728,7 @@ For attributes, we have three different comparison types: `ANY`, `ALL`, and`MAJO If you have defined a custom `AccessDecisionManager`, the comparison type is effectively discarded, as it is used only to create a default manager. -#### [](#generic-expression-usage)Generic Expression Usage +#### Generic Expression Usage Security expressions must return either `TRUE` or `FALSE`. @@ -3753,12 +3753,12 @@ describes the most often used built-in expressions: | `hasPermission(Object target, Object permission)` | Returns `true` if the user has access to the provided target for the
given permission — for example, `hasPermission(domainObject, 'read')`. | |`hasPermission(Object targetId, String targetType, Object
permission)`| Returns `true` if the user has access to the provided target for the
given permission — for example, `hasPermission(1,
'com.example.domain.Message', 'read')`. | -#### [](#event-attributes)Event Attributes +#### Event Attributes You can match an event ID by using a prefix of `EVENT_`. For example, matching event `A` would match an attribute of `EVENT_A`. -#### [](#event-expressions)Event Expressions +#### Event Expressions The base class for the expression root object for events is`EventSecurityExpressionRoot`. It provides access to a `Message`object, which is passed around with eventing. `EventSecurityExpressionRoot`has only one method, which the following table describes: @@ -3766,11 +3766,11 @@ The base class for the expression root object for events is`EventSecurityExpress |------------------------|------------------------------------------------| |`hasEvent(Object event)`|Returns `true` if the event matches given event.| -#### [](#transition-attributes)Transition Attributes +#### Transition Attributes When matching transition sources and targets, you can use the`TRANSITION_SOURCE_` and `TRANSITION_TARGET_` prefixes respectively. -#### [](#transition-expressions)Transition Expressions +#### Transition Expressions The base class for the expression root object for transitions is`TransitionSecurityExpressionRoot`. It provides access to a`Transition`object, which is passed around for transition changes.`TransitionSecurityExpressionRoot` has two methods, which the following table describes: @@ -3780,7 +3780,7 @@ table describes: |`hasSource(Object source)`|Returns `true` if the transition source matches given source.| |`hasTarget(Object target)`|Returns `true` if the transition target matches given target.| -### [](#sm-security-details)Understanding Security +### Understanding Security This section provides more detailed information about how security works within a state machine. You may not really need to know, but it is @@ -3804,7 +3804,7 @@ By default, for events, voters (`EventExpressionVoter`, `EventVoter`, and`RoleVo By default, for transitions, voters (`TransitionExpressionVoter`,`TransitionVoter`, and `RoleVoter`) are added into an `AccessDecisionManager`. -## [](#sm-error-handling)State Machine Error Handling +## State Machine Error Handling If a state machine detects an internal error during a state transition logic, it may throw an exception. Before this exception is processed @@ -3937,20 +3937,20 @@ public void testActionEntryErrorWithEvent() throws Exception { | |Error in entry/exit actions will not prevent transition to happen.| |---|------------------------------------------------------------------| -## [](#sm-service)State Machine Services +## State Machine Services StateMachine services are higher-level implementations meant to provide more user-level functionalities to ease normal runtime operations. Currently, only one service interface (`StateMachineService`) exists. -### [](#sm-service-statemachineservice)Using `StateMachineService` +### Using `StateMachineService` `StateMachineService` is an interface that is meant to handle running machines and have simple methods to “acquire” and “release” machines. It has one default implementation, named `DefaultStateMachineService`. -## [](#sm-persist)Persisting a State Machine +## Persisting a State Machine Traditionally, an instance of a state machine is used as is within a running program. You can achieve more dynamic behavior by using @@ -3983,7 +3983,7 @@ change within a state machine. If this interceptor callback fails, you can halt the state change attempt and, instead of ending in an inconsistent state, you can then handle this error manually. See[Using `StateMachineInterceptor`](#sm-interceptor) for how to use interceptors. -### [](#sm-persist-statemachinecontext)Using `StateMachineContext` +### Using `StateMachineContext` You cannot persist a `StateMachine` by using normal java serialization, as the object graph is too rich and contains too many @@ -4002,7 +4002,7 @@ independently. | |The [Data Multi Persist](#statemachine-examples-datajpamultipersist) sample shows
how you can persist parallel regions.| |---|---------------------------------------------------------------------------------------------------------------------------| -### [](#sm-persist-statemachinepersister)Using `StateMachinePersister` +### Using `StateMachinePersister` Building a `StateMachineContext` and then restoring a state machine from it has always been a little bit of “black magic” if done @@ -4095,7 +4095,7 @@ persister.restore(stateMachine2, "myid"); assertThat(stateMachine2.getState().getIds()).containsExactly("S2"); ``` -### [](#sm-persist-redis)Using Redis +### Using Redis `RepositoryStateMachinePersist` (which implements`StateMachinePersist`) offers support for persisting a state machine into Redis. The specific implementation is a`RedisStateMachineContextRepository`, which uses `kryo` serialization to @@ -4109,7 +4109,7 @@ a `StateMachinePersist` and uses `String` as its context object. `RedisStateMachineContextRepository` needs a`RedisConnectionFactory` for it to work. We recommend using a`JedisConnectionFactory` for it, as the preceding example shows. -### [](#sm-persist-statemachineruntimepersister)Using `StateMachineRuntimePersister` +### Using `StateMachineRuntimePersister` `StateMachineRuntimePersister` is a simple extension to`StateMachinePersist` that adds an interface-level method to get`StateMachineInterceptor` associated with it. This interceptor is then required to persist a machine during state changes without needing to @@ -4122,21 +4122,21 @@ and `RedisPersistingStateMachineInterceptor`. | |See the [Data Persist](#statemachine-examples-datapersist) sample for detailed usage.| |---|-------------------------------------------------------------------------------------| -## [](#sm-boot)Spring Boot Support +## Spring Boot Support The auto-configuration module (`spring-statemachine-autoconfigure`) contains all the logic for integrating with Spring Boot, which provides functionality for auto-configuration and actuators. All you need is to have this Spring Statemachine library as part of a boot application. -### [](#sm-boot-monitoring)Monitoring and Tracing +### Monitoring and Tracing `BootStateMachineMonitor` is created automatically and associated with a state machine. `BootStateMachineMonitor` is a custom `StateMachineMonitor`implementation that integrates with Spring Boot’s `MeterRegistry` and endpoints through a custom `StateMachineTraceRepository`. Optionally, you can disable this auto-configuration by setting the `spring.statemachine.monitor.enabled` key to`false`. The[Monitoring](#statemachine-examples-monitoring) sample shows how to use this auto-configuration. -### [](#repository-config)Repository Config +### Repository Config If the required classes are found from the classpath, Spring Data Repositories and entity class scanning is automatically auto-configured @@ -4144,7 +4144,7 @@ for [Repository Support](#sm-repository). The currently supported configurations are `JPA`, `Redis`, and`MongoDB`. You can disable repository auto-configuration by using the`spring.statemachine.data.jpa.repositories.enabled`,`spring.statemachine.data.redis.repositories.enabled` and`spring.statemachine.data.mongo.repositories.enabled` properties, respectively. -## [](#sm-monitoring)Monitoring a State Machine +## Monitoring a State Machine You can use `StateMachineMonitor` to get more information about the durations of how long transitions and actions take to execute. The following listing @@ -4208,7 +4208,7 @@ public class Config1 extends StateMachineConfigurerAdapter { | |See the [Monitoring](#statemachine-examples-monitoring) sample for detailed usage.| |---|----------------------------------------------------------------------------------| -## [](#sm-distributed)Using Distributed States +## Using Distributed States Distributed state is probably one of a most complicated concepts of a Spring state machine. What exactly is a distributed state? A state @@ -4286,7 +4286,7 @@ public class Config You can find the current technical documentation for a Zookeeker-based distributed state machine [in the appendix](#appendices-zookeeper). -### [](#using-zookeeperstatemachineensemble)Using `ZookeeperStateMachineEnsemble` +### Using `ZookeeperStateMachineEnsemble` `ZookeeperStateMachineEnsemble` itself needs two mandatory settings, an instance of `curatorClient` and a `basePath`. The client is a`CuratorFramework`, and the path is the root of a tree in a `Zookeeper` instance. @@ -4303,7 +4303,7 @@ size of the log, it is put into an error state and disconnected from the ensemble, indicating it has lost its history and its ability to fully reconstruct the synchronized status. -## [](#sm-test)Testing Support +## Testing Support We have also added a set of utility classes to ease testing of state machine instances. These are used in the framework itself but are also @@ -4391,7 +4391,7 @@ import static org.hamcrest.collection.IsMapContaining.hasEntry; | |All possible options for expected results are documented in the Javadoc for[`StateMachineTestPlanStepBuilder`](https://docs.spring.io/spring-statemachine/docs/3.0.1/api/org/springframework/statemachine/test/StateMachineTestPlanBuilder.StateMachineTestPlanStepBuilder.html).| |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-papyrus)Eclipse Modeling Support +## Eclipse Modeling Support Defining a state machine configuration with UI modeling is supported through the Eclipse Papyrus framework. @@ -4431,7 +4431,7 @@ Behind the scenes, a raw UML file would look like the following example: | |When opening an existing model that has been defined as UML, you have three
files: `.di`, `.notation`, and `.uml`. If a model was not created in your
eclipse’s session, it does not understand how to open an actual state
chart. This is a known issue in the Papyrus plugin, and there is an easy
workaround. In a Papyrus perspective, you can see a model explorer for
your model. Double click Diagram StateMachine Diagram, which
instructs Eclipse to open this specific model in its proper Papyrus
modeling plugin.| |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#using-umlstatemachinemodelfactory)Using `UmlStateMachineModelFactory` +### Using `UmlStateMachineModelFactory` After a UML file is in place in your project, you can import it into your configuration by using `StateMachineModelConfigurer`, where`StateMachineModelFactory` is associated with a model.`UmlStateMachineModelFactory` is a special factory that knows how to @@ -4476,7 +4476,7 @@ functionalities up to the actual implementation. The following sections go through how Spring Statemachine implements UML models based on the Eclipse Papyrus plugin. -#### [](#sm-papyrus-statemachinecomponentresolver)Using `StateMachineComponentResolver` +#### Using `StateMachineComponentResolver` The next example shows how `UmlStateMachineModelFactory` is defined with a `StateMachineComponentResolver`, which registers the`myAction` and `myGuard` functions, respectively. Note that these components @@ -4531,7 +4531,7 @@ public static class Config2 extends StateMachineConfigurerAdapteranonymous transition.| |---|---------------------------------------------------------------------------------| -### [](#defining-timers)Defining Timers +### Defining Timers Transitions can also happen based on timed events. Spring Statemachine support two types of timers, ones which fires continuously on a @@ -4631,7 +4631,7 @@ shows the result: Then the user can pick one of these timed events instead of a signal event for a particular transition. -### [](#sm-papyrus-choice)Defining a Choice +### Defining a Choice A choice is defined by drawing one incoming transition into a CHOICE state and drawing multiple outgoing transitions from it to target @@ -4651,11 +4651,11 @@ The following image shows the result of making a choice with three branches: | |Junction works similarly same, except that it allows multiple incoming
transitions. Thus, its behavior compared to Choice is purely
academic. The actual logic to select the outgoing transition is exactly the same.| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#defining-a-junction)Defining a Junction +### Defining a Junction See [Defining a Choice](#sm-papyrus-choice). -### [](#defining-entry-and-exit-points)Defining Entry and Exit Points +### Defining Entry and Exit Points You can use EntryPoint and ExitPoint to create controlled entry and exit with states that have sub-states. In the following state chart, events `E1` and`E2` have normal state behavior by entering and exiting state`S2`, where normal state behavior happens by entering initial state`S21`. @@ -4673,20 +4673,20 @@ respectively. The following image shows the result: | |If state is defined as a sub-machine reference and you need to use entry and exit points,
you must externally define a ConnectionPointReference, with
its entry and exit reference set to point to a correct entry or exit point
within a submachine reference. Only after that, is it possible to
target a transition that correctly links from the outside to the inside of
a sub-machine reference. With ConnectionPointReference, you may need
to find these settings from Properties → Advanced → UML →
Entry/Exit. The UML specification lets you define multiple entries and exits. However,
with a state machine, only one is allowed.| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#defining-history-states)Defining History States +### Defining History States When working with history states, three different concepts are in play. UML defines a Deep History and a Shallow History. The Default History State comes into play when history state is not yet known. These are represented in following sections. -#### [](#shallow-history)Shallow History +#### Shallow History In the following image, Shallow History is selected and a transition is defined into it: ![papyrus gs 18](images/papyrus-gs-18.png) -#### [](#deep-history)Deep History +#### Deep History Deep History is used for state that has other deep nested states, thus giving a chance to save whole nested state structure. @@ -4694,7 +4694,7 @@ The following image shows a definition that uses Deep History: ![papyrus gs 19](images/papyrus-gs-19.png) -#### [](#default-history)Default History +#### Default History In cases where a Transition terminates on a history when the state has not been entered before it had reached its @@ -4708,7 +4708,7 @@ never been active, as its history has never been recorded. If state`S2` has been ![papyrus gs 20](images/papyrus-gs-20.png) -### [](#defining-forks-and-joins)Defining Forks and Joins +### Defining Forks and Joins Both Fork and Join are represented as bars in Papyrus. As shown in the next image, you need to draw one outgoing transition from `FORK` into state`S2` to have orthogonal regions. `JOIN` is then the reverse, where @@ -4716,37 +4716,37 @@ joined states are collected together from incoming transitions. ![papyrus gs 21](images/papyrus-gs-21.png) -### [](#defining-actions)Defining Actions +### Defining Actions You can assoiate swtate entry and exit actions by using a behavior. For more about this, see [Defining a Bean Reference](#sm-papyrus-beanref). -#### [](#using-an-initial-action)Using an Initial Action +#### Using an Initial Action An initial action (as shown in [Configuring Actions](#statemachine-config-actions)) is defined in UML by adding an action in the transition that leads from the Initial State marker into the actual state. This Action is then run when the state machine is started. -### [](#defining-guards)Defining Guards +### Defining Guards You can define a guard by first adding a Constraint and then defining its Specification as OpaqueExpression, which works in the same way as [Defining a Bean Reference](#sm-papyrus-beanref). -### [](#sm-papyrus-beanref)Defining a Bean Reference +### Defining a Bean Reference When you need to make a bean reference in any UML effect, action, or guard, you can do so with`FunctionBehavior` or `OpaqueBehavior`, where the defined language needs to be `bean` and the language body msut have a bean reference id. -### [](#sm-papyrus-spelref)Defining a SpEL Reference +### Defining a SpEL Reference When you need to use a SpEL expression instead of a bean reference in any UML effect, action, or guard, you can do so by using`FunctionBehavior` or `OpaqueBehavior`, where the defined language needs to be `spel` and the language body must be a SpEL expression. -### [](#sm-papyrus-submachineref)Using a Sub-Machine Reference +### Using a Sub-Machine Reference Normally, when you use sub-states, you draw those into the state chart itself. The chart may become too complex and big to @@ -4772,7 +4772,7 @@ sub-state. ![papyrus gs 15](images/papyrus-gs-15.png) -### [](#sm-papyrus-import)Using a Machine Import +### Using a Machine Import It’s also possible to use import functionality where uml files can reference to other models. @@ -4805,12 +4805,12 @@ public static class Config3 extends StateMachineConfigurerAdapterotherwise things break when model files are copied out from a
classpath to a temporary directory so that eclipse parsing classes can
read those.| |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-repository)Repository Support +## Repository Support This section contains documentation related to using 'Spring Data Repositories' in Spring Statemachine. -### [](#sm-repository-config)Repository Configuration +### Repository Configuration You can keep machine configuration in external storage, from which it can be loaded on demand, instead of creating a static @@ -4853,7 +4853,7 @@ Figure 2. SimpleSubMachine Figure 3. ShowcaseMachine -#### [](#sm-repository-config-jpa)JPA +#### JPA The actual repository implementations for JPA are`JpaStateRepository`, `JpaTransitionRepository`, `JpaActionRepository`, and `JpaGuardRepository`, which are backed by the @@ -5051,7 +5051,7 @@ You can find a complete example[here](#statemachine-examples-datajpa). This exam pre-populate a repository from an existing JSON file that has definitions for entity classes. -#### [](#sm-repository-config-redis)Redis +#### Redis The actual repository implementations for a Redis instance are`RedisStateRepository`, `RedisTransitionRepository`, `RedisActionRepository`, and `RedisGuardRepository`, which are backed by the @@ -5110,7 +5110,7 @@ void addConfig() { } ``` -#### [](#sm-repository-config-mongodb)MongoDB +#### MongoDB The actual repository implementations for a MongoDB instance are`MongoDbStateRepository`, `MongoDbTransitionRepository`, `MongoDbActionRepository`, and `MongoDbGuardRepository`, which are backed by the @@ -5178,7 +5178,7 @@ void addConfig() { } ``` -### [](#sm-repository-persistence)Repository Persistence +### Repository Persistence Apart from storing machine configuration (as shown in[Repository Configuration](#sm-repository-config)), in an external repository, you canx also persist machines into repositories. @@ -5186,7 +5186,7 @@ persist machines into repositories. The `StateMachineRepository` interface is a central access point that interacts with machine persistence and is backed by the entity class`RepositoryStateMachine`. -#### [](#sm-repository-persistence-jpa)JPA +#### JPA The actual repository implementation for JPA is`JpaStateMachineRepository`, which is backed by the entity class`JpaRepositoryStateMachine`. @@ -5208,7 +5208,7 @@ void persist() { } ``` -#### [](#sm-repository-persistence-redis)Redis +#### Redis The actual repository implementation for a Redis is`RedisStateMachineRepository`, which is backed by the entity class`RedisRepositoryStateMachine`. @@ -5230,7 +5230,7 @@ void persist() { } ``` -#### [](#sm-repository-persistence-mongodb)MongoDB +#### MongoDB The actual repository implementation for MongoDB is`MongoDbStateMachineRepository`, which is backed by the entity class`MongoDbRepositoryStateMachine`. @@ -5252,7 +5252,7 @@ void persist() { } ``` -# [](#statemachine-recipes)Recipes +# Recipes This chapter contains documentation for existing built-in state machine recipes. @@ -5269,7 +5269,7 @@ make it easy for you to reuse and extend. | |Recipes are a great way to make external contributions to the Spring
Statemachine project. If you are not ready to contribute to the
framework core itself, a custom and common recipe is a great way to
share functionality with other users.| |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#statemachine-recipes-persist)Persist +## Persist The persist recipe is a simple utility that lets you use a single state machine instance to persist and update the state of an arbitrary item in @@ -5288,7 +5288,7 @@ The recipe’s main class is `PersistStateMachineHandler`, which makes three ass You can find a sample that shows how to use this recipe at[Persist](#statemachine-examples-persist). -## [](#statemachine-recipes-tasks)Tasks +## Tasks The tasks recipe is a concept to run DAG (Directed Acrylic Graph) of `Runnable` instances that use a state machine. This recipe has been developed from ideas introduced @@ -5459,7 +5459,7 @@ TasksHandler handler = TasksHandler.builder() handler.continueFromError(); ``` -# [](#statemachine-examples)State Machine Examples +# State Machine Examples This part of the reference documentation explains the use of state machines together with sample code and UML state charts. We use a few @@ -5521,7 +5521,7 @@ project’s `build/libs` directory. | |The filenames for the jars to which we refer in this section are populated during a
build of this document, meaning that, if you build samples from
master, you have files with a `BUILD-SNAPSHOT` postfix.| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#statemachine-examples-turnstile)Turnstile +## Turnstile Turnstile is a simple device that gives you access if payment is made. It is a concept that is simple to model using a state machine. In its @@ -5630,7 +5630,7 @@ State changed to LOCKED Event PUSH send ``` -## [](#statemachine-examples-turnstilereactive)Turnstile Reactive +## Turnstile Reactive Turnstile reactive is an enhacement to [Turnstile](#statemachine-examples-turnstile) sample using same *StateMachine* concept and adding a reactive web layer communicating reactively with @@ -5740,7 +5740,7 @@ Response then contains results for both events: ] ``` -## [](#statemachine-examples-showcase)Showcase +## Showcase Showcase is a complex state machine that shows all possible transition topologies up to four levels of state nesting. @@ -6075,7 +6075,7 @@ In the preceding sample: matches to its dummy transition without guard or action, so it never happens. -## [](#statemachine-examples-cdplayer)CD Player +## CD Player CD Player is a sample which resembles a use case that many people have used in the real world. CD Player itself is a really simple entity that allows a @@ -6547,7 +6547,7 @@ In the preceding run: * We stop playing. -## [](#statemachine-examples-tasks)Tasks +## Tasks The Tasks sample demonstrates parallel task handling within regions and adds error handling to either @@ -6877,7 +6877,7 @@ In the precding example, if we simulate failure for either task `T2` or `T3`, th machine goes to the `MANUAL` state, where problem needs to be fixed manually before it can go back to the `READY` state. -## [](#statemachine-examples-washer)Washer +## Washer The washer sample demonstrates how to use a history state to recover a running state configuration with a simulated power-off situation. @@ -7012,7 +7012,7 @@ In the preceding run: * The state is restored from the HISTORY state, which takes state machine back to its previous known state. -## [](#statemachine-examples-persist)Persist +## Persist Persist is a sample that uses the [Persist](#statemachine-recipes-persist) recipe to demonstrate how database entry update logic can be controlled by a @@ -7186,7 +7186,7 @@ private class LocalPersistStateChangeListener implements PersistStateChangeListe } ``` -## [](#statemachine-examples-zookeeper)Zookeeper +## Zookeeper Zookeeper is a distributed version from the[Turnstile](#statemachine-examples-turnstile) sample. @@ -7293,7 +7293,7 @@ sm>Exit state UNLOCKED Entry state LOCKED ``` -## [](#statemachine-examples-web)Web +## Web Web is a distributed state machine example that uses a zookeeper state machine to handle distributed state. See [Zookeeper](#statemachine-examples-zookeeper). @@ -7353,7 +7353,7 @@ all of the browsers. The following image shows the result in one browser: sm dist n1 4 -## [](#statemachine-examples-scope)Scope +## Scope Scope is a state machine example that uses session scope to provide an individual instance for every user. @@ -7378,7 +7378,7 @@ The following image shows the state machine in a browser: sm scope 1 -## [](#statemachine-examples-security)Security +## Security Security is a state machine example that uses most of the possible combinations of securing a state machine. It secures sending events, transitions, @@ -7516,7 +7516,7 @@ The transition itself is secured with a role of `ADMIN`, so this transition does not run if the current user does not hate that role. -## [](#statemachine-examples-eventservice)Event Service +## Event Service The event service example shows how you can use state machine concepts as a processing engine for events. This sample evolved from a question: @@ -7814,7 +7814,7 @@ The following image shows the result of these operations: sm eventservice 4 -## [](#statemachine-examples-deploy)Deploy +## Deploy The deploy example shows how you can use state machine concepts with UML modeling to provide a generic error handling state. This state @@ -7868,7 +7868,7 @@ In a browser, you can see something like the following image: Now you can start to send events to a machine and choose various message headers to drive functionality. -## [](#statemachine-examples-ordershipping)Order Shipping +## Order Shipping The order shipping example shows how you can use state machine concepts to build a simple order processing system. @@ -7920,7 +7920,7 @@ Finally, you can see what machine does by refreshing a page, as the following im ![sm ordershipping 4](images/sm-ordershipping-4.png) -## [](#statemachine-examples-datajpa)JPA Configuration +## JPA Configuration The JPA configuration example shows how you can use state machine concepts with a machine configuration kept in a database. This sample uses @@ -8073,7 +8073,7 @@ The following listing shows the source of the data with which we populate the da ] ``` -## [](#statemachine-examples-datapersist)Data Persist +## Data Persist The data persist sample shows how you can state machine concepts with a persisting machine in an external repository. This sample uses @@ -8231,7 +8231,7 @@ The following image shows the result of doing so: If you then request state machine `datajpapersist1` but send no events, the state machine is restored back to its persisted state, `S3`. -## [](#statemachine-examples-datajpamultipersist)Data Multi Persist +## Data Multi Persist The data multi ersist sample is an extension of two other samples:[JPA Configuration](#statemachine-examples-datajpa) and [Data Persist](#statemachine-examples-datapersist). We still keep machine configuration in a database and persist into a @@ -8362,7 +8362,7 @@ different regions in a database have different contexts: ![sm datajpamultipersist 3](images/sm-datajpamultipersist-3.png) -## [](#statemachine-examples-datajpapersist)Data JPA Persist +## Data JPA Persist The data persist sample shows how you can state machine concepts with a persisting machine in an external repository. This sample uses @@ -8520,7 +8520,7 @@ The following image shows the result of doing so: If you then request state machine `datajpapersist1` but send no events, the state machine is restored back to its persisted state, `S3`. -## [](#statemachine-examples-monitoring)Monitoring +## Monitoring The monitoring sample shows how you can use state machine concepts to monitor state machine transitions and actions. @@ -8667,11 +8667,11 @@ You can also view tracing from Spring Boot by running the following `curl`comman ] ``` -# [](#statemachine-faq)FAQ +# FAQ This chapter answers the questions that Spring Statemachine users most often ask. -## [](#state-changes)State Changes +## State Changes How can I automatically transit to the next state? @@ -8689,7 +8689,7 @@ You can choose from three approaches: state transition into the next state when state is entered and its actions has been completed. -## [](#extended-state)Extended State +## Extended State How I can initialize variables on state machine start? @@ -8701,14 +8701,14 @@ this initial transition, you can run a simple action that, within a `StateContext`, can do whatever it likes with extended state variables. -# [](#appendices)Appendices +# Appendices -## [](#support-content)Appendix A: Support Content +## Appendix A: Support Content This appendix provides generic information about the classes and material that are used in this reference documentation. -### [](#classes-used-in-this-document)Classes Used in This Document +### Classes Used in This Document The following listings show the classes used throughout this reference guide: @@ -8739,11 +8739,11 @@ public enum Events { } ``` -## [](#state-machine-concepts)Appendix B: State Machine Concepts +## Appendix B: State Machine Concepts This appendix provides generial information about state machines. -### [](#quick-example)Quick Example +### Quick Example Assuming we have states named `STATE1` and `STATE2` and events named `EVENT1` and`EVENT2`, you can define the logic of the state machine as the following image shows: @@ -8823,7 +8823,7 @@ public class MyApp { } ``` -### [](#glossary)Glossary +### Glossary **State Machine** @@ -8920,12 +8920,12 @@ to `FALSE`. A action is a behavior run during the triggering of the transition. -### [](#crashcourse)A State Machine Crash Course +### A State Machine Crash Course This appendix provides a generic crash course to state machine concepts. -#### [](#states)States +#### States A state is a model in which a state machine can be. It is always easier to describe state as a real world example rather than trying to use @@ -8944,7 +8944,7 @@ flags, nested if/else/break clauses, or other impractical (and sometimes tortuou rely on state, state variables, or another interaction with a state machine. -#### [](#pseudo-states)Pseudo States +#### Pseudo States Pseudostate is a special type of state that usually introduces more higher-level logic into a state machine by either giving a state a @@ -8952,7 +8952,7 @@ special meaning (such as initial state). A state machine can then internally react to these states by doing various actions that are available in UML state machine concepts. -##### [](#initial)Initial +##### Initial The **Initial pseudostate** state is always needed for every single state machine, whether you have a simple one-level state machine or a more @@ -8960,7 +8960,7 @@ complex state machine composed of submachines or regions. The initial state defines where a state machine should go when it starts. Without it, a state machine is ill-formed. -##### [](#end)End +##### End The **Terminate pseudostate** (which is also called “end state”) indicates that a particular state machine has reached its final state. Effectively, @@ -8968,7 +8968,7 @@ this mean that a state machine no longer processes any events and does not transit to any other state. However, in the case where submachines are regions, a state machine can restart from its terminal state. -##### [](#choice)Choice +##### Choice You can use the **Choice pseudostate** choose a dynamic conditional branch of a transition from this state. The dynamic condition is evaluated by guards @@ -8977,7 +8977,7 @@ simple if/elseif/else structure is used to make sure that one branch is selected. Otherwise, the state machine might end up in a deadlock, and the configuration is ill-formed. -##### [](#junction)Junction +##### Junction The **Junction pseudostate** is functionally similar to choice, as both are implemented with if/elseif/else structures. The only real difference is @@ -8986,7 +8986,7 @@ allows only one. Thus difference is largely academic but does have some differences, such as when a state machine is designed is used with a real UI modeling framework. -##### [](#history)History +##### History You can use the **History pseudostate** to remember the last active state configuration. After a state machine has exited, you can use a history state @@ -9013,7 +9013,7 @@ active. Otherwise, the normal history entry into the region is executed. If no default history transition is defined, the standard default entry of the region is performed. -##### [](#fork)Fork +##### Fork You can use the **Fork pseudostate** to do an explicit entry into one or more regions. The following image shows how a fork works: @@ -9025,7 +9025,7 @@ means that regions are activated by entering their initial states. You can also add targets directly to any state in a region, which allows more controlled entry into a state. -##### [](#join)Join +##### Join The **Join pseudostate** merges together several transitions that originate from different regions. It is generally used to wait @@ -9039,7 +9039,7 @@ join states are the terminal states of the participating regions. You can also define source states to be any state in a region, which allows controlled exit from regions. -##### [](#entry-point)Entry Point +##### Entry Point An **Entry Point pseudostate** represents an entry point for a state machine or a composite state that provides encapsulation of the insides @@ -9047,7 +9047,7 @@ of the state or state machine. In each region of the state machine or composite state that owns the entry point, there is at most a single transition from the entry point to a vertex within that region. -##### [](#exit-point)Exit Point +##### Exit Point An **Exit Point pseudostate** is an exit point of a state machine or composite state that provides encapsulation of the insides of the state @@ -9056,7 +9056,7 @@ region of the composite state (or a state machine referenced by a submachine state) imply exiting of this composite state or submachine state (with execution of its associated exit behavior). -#### [](#guard-conditions)Guard Conditions +#### Guard Conditions Guard conditions are expressions which evaluates to either `TRUE` or`FALSE`, based on extended state variables and event parameters. Guards are used with actions and transitions to dynamically choose whether a @@ -9064,7 +9064,7 @@ particular action or transition should be run. The various spects of guards, event parameters, and extended state variables exist to make state machine design much more simple. -#### [](#events)Events +#### Events Event is the most-used trigger behavior to drive a state machine. There are other ways to trigger behavior in a state machine @@ -9072,20 +9072,20 @@ There are other ways to trigger behavior in a state machine interact with a state machine. Events are also called “signals”. They basically indicate something that can possibly alter a state machine state. -#### [](#transitions)Transitions +#### Transitions A transition is a relationship between a source state and a target state. A switch from one state to another is a state transition caused by a trigger. -##### [](#internal-transition)Internal Transition +##### Internal Transition Internal transition is used when an action needs to be run without causing a state transition. In an internal transition, the source state and the target state is always the same, and it is identical with a self-transition in the absence of state entry and exit actions. -##### [](#external-versus-local-transitions)External versus Local Transitions +##### External versus Local Transitions In most cases, external and local transitions are functionally equivalent, except in cases where the transition happens between super @@ -9098,11 +9098,11 @@ with very simplistic super and sub states: statechart4 -#### [](#triggers)Triggers +#### Triggers A trigger begins a transition. Triggers can be driven by either events or timers. -#### [](#actions)Actions +#### Actions Actions really glue state machine state changes to a user’s own code. A state machine can run an action on various @@ -9116,7 +9116,7 @@ access extended state variables, event headers (if a transition is based on an event), or an actual transition (where it is possible to see more detailed about where this state change is coming from and where it is going). -#### [](#hierarchical-state-machines)Hierarchical State Machines +#### Hierarchical State Machines The concept of a hierarchical state machine is used to simplify state design when particular states must exist together. @@ -9132,7 +9132,7 @@ state is able to handle an event, together with transition guard conditions. If these conditions do not evaluate to `TRUE`, the state machine merely see what the super state can handle. -#### [](#regions)Regions +#### Regions Regions (which are also called as orthogonal regions) are usually viewed as exclusive OR (XOR) operations applied to states. The concept of a region in @@ -9155,12 +9155,12 @@ still work together in some fashion. This independence lets orthogonal regions combine together in multiple simultaneous states within a single state in a state machine. -## [](#appendices-zookeeper)Appendix C: Distributed State Machine Technical Paper +## Appendix C: Distributed State Machine Technical Paper This appendix provides more detailed technical documentation about using a Zookeeper instance with Spring Statemachine. -### [](#abstract)Abstract +### Abstract Introducing a “distributed state” on top of a single state machine instance running on a single JVM is a difficult and a complex topic. @@ -9190,7 +9190,7 @@ states. This model aims to provide good methods for cloud applications to have much easier ways to communicate with each other without having to explicitly build these distributed state concepts. -### [](#state-machine-technical-paper-introduction)Introduction +### Introduction Spring State Machine is not forced to use a single threaded execution model, because, once multiple regions are used, regions can be executed in @@ -9239,7 +9239,7 @@ Jepsen tests in the following environment: All Jepsen tests for `Spring Distributed Statemachine` are available from[Jepsen Tests.](https://github.com/spring-projects/spring-statemachine/tree/master/jepsen/spring-statemachine-jepsen) -### [](#generic-concepts)Generic Concepts +### Generic Concepts One design decision of a `Distributed State Machine` was not to make each individual state machine instance be aware that it is part of a @@ -9262,12 +9262,12 @@ As mentioned in [Using Distributed States](#sm-distributed), distributed states wrapping an instance of a `StateMachine` in a`DistributedStateMachine`. The specific `StateMachineEnsemble`implementation is `ZookeeperStateMachineEnsemble` provides integration with Zookeeper. -### [](#the-role-of-zookeeperstatemachinepersist)The Role of `ZookeeperStateMachinePersist` +### The Role of `ZookeeperStateMachinePersist` We wanted to have a generic interface (`StateMachinePersist`) that Can persist `StateMachineContext` into arbitrary storage and`ZookeeperStateMachinePersist` implements this interface for`Zookeeper`. -### [](#the-role-of-zookeeperstatemachineensemble)The Role of `ZookeeperStateMachineEnsemble` +### The Role of `ZookeeperStateMachineEnsemble` While a distributed state machine uses one set of serialized contexts to update its own state, with zookeeper, we have a @@ -9294,7 +9294,7 @@ The size of a circular buffer is mandated to be a power of two, to avoid trouble when the integer goes to overflow. For this reason, we need not handle any specific cases. -### [](#distributed-tolerance)Distributed Tolerance +### Distributed Tolerance To show how a various distributed actions against a state machine work in real life, we use a set of Jepsen tests to @@ -9310,7 +9310,7 @@ Jepsen, to simulate network conditions. The plotted graphs shown later in this chapter contain states and events that directly map to a state chart, which you can be find in[Web](#statemachine-examples-web). -#### [](#sm-tech-isolated-events)Isolated Events +#### Isolated Events Sending an isolated single event into exactly one state machine in an ensemble is the simplest testing scenario and demonstrates that a @@ -9341,7 +9341,7 @@ In the preceding image: * We cycle events `I`, `C`, `I`, and `K` one more time, through random nodes. -#### [](#parallel-events)Parallel Events +#### Parallel Events One logical problem with multiple distributed state machines is that, if the same event is sent into multiple state machines at exactly the same @@ -9363,7 +9363,7 @@ In the preceding image, we use the same event flow that we used in the previous ([Isolated Events](#sm-tech-isolated-events)), with the difference that events are always sent to all nodes. -#### [](#concurrent-extended-state-variable-changes)Concurrent Extended State Variable Changes +#### Concurrent Extended State Variable Changes Extended state machine variables are not guaranteed to be atomic at any given time, but, after a distributed state change, all state machines @@ -9382,7 +9382,7 @@ In the preceding image: * Event `J` is repeated from variable `v2` to `v8`, doing the same checks. -#### [](#partition-tolerance)Partition Tolerance +#### Partition Tolerance We need to always assume that, sooner or later, things in a cluster go bad, whether it is a crash of a Zookeeper instance, a state @@ -9458,7 +9458,7 @@ In the preceding image: * Finally, event `K1` is sent to all state machines to ensure that ensemble workS properly. This state change leads back to state`S21`. -#### [](#crash-and-join-tolerance)Crash and Join Tolerance +#### Crash and Join Tolerance In this test, we demonstrate that killing an existing state machine and then joining a new instance back into an ensemble keeps the @@ -9483,13 +9483,13 @@ In the preceding image: * Finally, we do a simple transition back to `S21` from `S211` to make sure that all state machines still function properly. -## [](#devdocs)Developer Documentation +## Developer Documentation This appendix provides generic information for adevelopers who may want to contribute or other people who want to understand how state machine works or understand its internal concepts. -### [](#devdocs-configmodel)StateMachine Config Model +### StateMachine Config Model `StateMachineModel` and other related SPI classes are an abstraction between various configuration and factory classes. This also allows @@ -9523,7 +9523,7 @@ ObjectStateMachineFactory factory = new ObjectStateMachineFactor StateMachine stateMachine = factory.getStateMachine(); ``` -## [](#appendix-reactormigrationguide)Appendix D: Reactor Migration Guide +## Appendix D: Reactor Migration Guide Main task for a work for `3.x` has been to both internally and externally to move and change as much as we can from imperative code into a reactive world. This means that some @@ -9531,7 +9531,7 @@ of a main interfaces has added a new reative methods and most of a internal exec (where applicable) has been moved over to handled by a reactor. Essentially what this means is that thread handling model is considerably different compared to `2.x`. Following chapters go throught all these changes. -### [](#communicating-with-a-machine)Communicating with a Machine +### Communicating with a Machine We’ve added new reactive methods to `StateMachine` while still keeping old blocking event methods in place. @@ -9580,7 +9580,7 @@ but are deprecated to get removed in future releases. boolean accepted = machine.sendEvent("EVENT"); ``` -### [](#taskexecutor-and-taskscheduler)TaskExecutor and TaskScheduler +### TaskExecutor and TaskScheduler StateMachine execution with `TaskExecutor` and state action scheduling with `TaskScheduler`has been fully replaced in favour or Reactor execution and scheduling. @@ -9588,7 +9588,7 @@ Essentially execution outside of a main thread is needed in two places, firstly be always be executed independently. Currently we’ve chosen to just use *Reactor*`Schedulers.parallel()` for these which should give relatively good results as it tries to automatically use available number of cpu cores from a system. -### [](#reactive-examples)Reactive Examples +### Reactive Examples While most of an examples are still same, we’ve overhauled some of them and created some new: diff --git a/docs/spring-statemachine/spring-statemachine.md b/docs/spring-statemachine/spring-statemachine.md index 5f13e4f..34aecbe 100644 --- a/docs/spring-statemachine/spring-statemachine.md +++ b/docs/spring-statemachine/spring-statemachine.md @@ -1,6 +1,6 @@ # Spring StateMachine 文档 -## [](#preface)序言 +## 序言 状态机的概念很可能比这个引用文档的任何读者都要古老,而且肯定比 Java 语言本身还要古老。对有限自动机的描述可以追溯到 1943 年,当时 Warren McCulloch 先生和 Walter Pitts 先生写了一篇关于它的论文。后来,George H.Mealy 在 1955 年提出了一个状态机概念(称为“Mealy Machine”)。一年后的 1956 年,Edward F.Moore 发表了另一篇论文,他在论文中描述了所谓的“摩尔机器”。如果你曾经读过任何有关状态机的东西,那么 Mealy 和 Moore 这两个名字应该是在某个时候出现的。 @@ -16,7 +16,7 @@ [Appendices](#appendices)包含有关已用材料和状态机的通用信息。 -# [](#introduction)引言 +# 引言 Spring StateMachine 是一种允许应用程序开发人员在 Spring 应用程序中使用传统状态机概念的框架。SSM 提供以下功能: @@ -36,7 +36,7 @@ Spring StateMachine 是一种允许应用程序开发人员在 Spring 应用程 在继续之前,我们建议先查看一下附录[Glossary](#glossary)和[状态机速成课程](#crashcourse),以了解状态机是什么。文档的其余部分希望你熟悉状态机的概念。 -## [](#background)背景 +## 背景 状态机是强大的,因为它们的行为总是被保证是一致的,并且由于操作规则是在机器启动时用石头写成的,因此相对容易地进行调试。其思想是,你的应用程序现在处于并且可能存在于有限数量的状态中。然后会发生一些事情,将你的应用程序从一个州带到另一个州。状态机由触发器驱动,触发器基于事件或计时器。 @@ -44,7 +44,7 @@ Spring StateMachine 是一种允许应用程序开发人员在 Spring 应用程 传统上,当开发人员意识到代码库开始看起来像一盘满是意大利面条的盘子时,状态机就会被添加到现有的项目中。意大利面条代码看起来像是一个永无止境的,if,else 和 break 子句的层次结构,当事情开始看起来太复杂时,编译器可能应该要求开发人员回家。 -## [](#usage-scenarios)使用场景 +## 使用场景 当出现以下情况时,项目是使用状态机的一个很好的候选者: @@ -62,17 +62,17 @@ Spring StateMachine 是一种允许应用程序开发人员在 Spring 应用程 * 在一个 if-else 结构(或者更糟糕的是,多个这样的结构)中循环,检查是否设置了特定的标志或枚举,然后在你的标志和枚举的某些组合存在或不存在时,针对该做什么做出进一步的例外。 -# [](#statemachine-getting-started)入门 +# 入门 如果你刚刚开始使用 Spring Statemachine,这是适合你的一节!这里,我们回答基本的“`what?`”、“`how?`”和“`why?`”问题。我们从温和地介绍 Spring 静态机械开始。然后,我们构建了我们的第一个 Spring Statemachine 应用程序,并讨论了一些核心原则。 -## [](#system-requirement)系统需求 +## 系统需求 Spring StateMachine3.0.1 是用 JDK8(所有工件都具有 JDK7 兼容性)和 Spring Framework5.3.8 构建和测试的。在其核心系统中,它不需要 Spring 框架之外的任何其他依赖关系。 其他可选部分(例如[使用分布状态](#sm-distributed))对 ZooKeeper 具有依赖性,而[状态机示例](#statemachine-examples)则对`spring-shell`和`spring-boot`具有依赖性,这会将其他依赖性拉出框架本身之外。此外,可选的安全和数据访问功能具有对 Spring 安全和 Spring 数据模块的依赖性。 -## [](#modules)模块 +## 模块 下表描述了 Spring Statemachine 可用的模块。 @@ -93,7 +93,7 @@ Spring StateMachine3.0.1 是用 JDK8(所有工件都具有 JDK7 兼容性) | `spring-statemachine-bom` |材料清单 POM。| | `spring-statemachine-starter` |Spring 引导启动器。| -## [](#using-gradle)使用 Gradle +## 使用 Gradle 下面的清单显示了一个典型的`build.gradle`文件,该文件是通过在[https://start.spring.io](https://start.spring.io)处选择各种设置来创建的: @@ -157,7 +157,7 @@ dependencyManagement { | |对于
产品开发,不需要`libs-milestone`和`libs-snapshot`存储库。| |---|----------------------------------------------------------------------------------------------------| -## [](#using-maven)使用 Maven +## 使用 Maven 下面的示例显示了一个典型的`pom.xml`文件,该文件是通过在[https://start.spring.io](https://start.spring.io)处选择各种选项创建的: @@ -278,7 +278,7 @@ dependencyManagement { | |对于
产品开发,不需要`libs-milestone`和`libs-snapshot`存储库。| |---|-----------------------------------------------------------------------------------------------------| -## [](#developing-your-first-spring-statemachine-application)开发你的第一个 Spring Statemachine 应用程序 +## 开发你的第一个 Spring Statemachine 应用程序 你可以从创建一个简单的 Spring boot`Application`类开始,该类实现`CommandLineRunner`。下面的示例展示了如何做到这一点: @@ -379,9 +379,9 @@ State change to S2 这些行表示你构造的机器正在从一种状态移动到另一种状态,正如它应该的那样。 -# [](#whatsnew)最新更新 +# 最新更新 -## [](#in-1-1)in1.1 +## in1.1 Spring StateMachine1.1 专注于安全性和与 Web 应用程序的更好的互操作性。它包括以下内容: @@ -409,7 +409,7 @@ Spring StateMachine1.1 专注于安全性和与 Web 应用程序的更好的互 * 使用 Eclipse Papyrus 的 UI 建模支持。见[Eclipse 建模支持](#sm-papyrus)。 -## [](#in-1-2)in1.2 +## in1.2 Spring StateMachine1.2 侧重于通用增强、更好的 UML 支持以及与外部配置存储库的集成。它包括以下内容: @@ -427,7 +427,7 @@ Spring StateMachine1.2 侧重于通用增强、更好的 UML 支持以及与外 * 支持跟踪和监视。见[监视状态机](#sm-monitoring)。 -### [](#in-1-2-8)in1.2.8 +### in1.2.8 Spring Statemachine1.2.8 所包含的功能比在点释放中通常看不到的多一点,但是这些变化不值得 Spring Statemachine1.3 的叉子。它包括以下内容: @@ -439,11 +439,11 @@ Spring Statemachine1.2.8 所包含的功能比在点释放中通常看不到的 * 过渡冲突政策。见[配置公共设置](#statemachine-config-commonsettings) -## [](#in-2-0)in2.0 +## in2.0 Spring StateMachine2.0 关注于 Spring Boot2.x 支持。 -### [](#in-2-0-0)in2.0.0 +### in2.0.0 Spring Statemachine2.0.0 包括以下内容: @@ -451,7 +451,7 @@ Spring Statemachine2.0.0 包括以下内容: * `spring-statemachine-boot`模块已重命名为`spring-statemachine-autoconfigure`。 -## [](#in-3-0)in3.0 +## in3.0 Spring Statemachine3.0.0 侧重于添加反应性支持。从`2.x`移动到`3.x`会引入一些突破性的变化,在[反应堆迁移指南](#appendix-reactormigrationguide)中有详细说明。 @@ -462,7 +462,7 @@ Spring Statemachine3.0.0 侧重于添加反应性支持。从`2.x`移动到`3.x` 在这一点上,大多数文档已经被更改为展示反应性接口,而我们仍然保留一些注释,以供仍在使用旧的阻塞方法的用户使用。 -# [](#statemachine)使用 Spring 安定 +# 使用 Spring 安定 参考文档的这一部分解释了 Spring StateMachine 向任何基于 Spring 的应用程序提供的核心功能。 @@ -516,14 +516,14 @@ Spring Statemachine3.0.0 侧重于添加反应性支持。从`2.x`移动到`3.x` * [存储库支持](#sm-repository)描述状态机存储库配置支持。 -## [](#sm-config)机械构型 +## 机械构型 使用状态机时的常见任务之一是设计其运行时配置。本章重点讨论如何配置 Spring StateMachine,以及如何利用 Spring 的轻量级 IoC 容器来简化应用程序内部,使其更易于管理。 | |本节中的配置示例没有完成功能。也就是说,
总是需要同时定义状态和转换。
否则,状态机配置将是格式错误的。我们有
,只是通过将其他需要的部分
留出来,使代码片段不那么冗长。| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#statemachine-config-annotations)使用`enable`注释 +### 使用`enable`注释 我们使用两个熟悉的 Spring *使能者*注释来简化配置:`@EnableStateMachine`和`@EnableStateMachineFactory`。当将这些注释放置在`@Configuration`类中时,将启用状态机所需的一些基本功能。 @@ -534,7 +534,7 @@ Spring Statemachine3.0.0 侧重于添加反应性支持。从`2.x`移动到`3.x` | |下面几节将展示这些方法的使用示例。| |---|----------------------------------------------------| -### [](#statemachine-config-states)配置状态 +### 配置状态 在本指南的后面,我们将介绍更复杂的配置示例,但我们首先从简单的内容开始。对于大多数简单的状态机,你可以使用`EnumStateMachineConfigurerAdapter`并定义可能的状态并选择初始和可选的结束状态。 @@ -581,7 +581,7 @@ public class Config1Strings | |使用枚举带来了一组更安全的状态和事件类型,但
限制了编译时间的可能组合。字符串没有这个
限制,允许你使用更动态的方式来构建状态
机器配置,但不允许相同级别的安全。| |---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#configuring-hierarchical-states)配置层次结构状态 +### 配置层次结构状态 可以通过使用多个`withStates()`调用来定义分层状态,其中可以使用`parent()`来指示这些特定状态是某些其他状态的子状态。下面的示例展示了如何做到这一点: @@ -608,7 +608,7 @@ public class Config2 } ``` -### [](#configuring-regions)配置区域 +### 配置区域 没有特殊的配置方法来将一组状态标记为正交状态的一部分。简单地说,当同一个层次状态机有多个状态集,每个状态集都有一个初始状态时,就会创建正交状态。因为单个状态机只能有一个初始状态,所以多个初始状态意味着一个特定的状态必须有多个独立的区域。下面的示例展示了如何定义区域: @@ -676,7 +676,7 @@ public class Config10RegionId } ``` -### [](#configuring-transitions)配置转换 +### 配置转换 我们支持三种不同类型的转换:`external`、`internal`和`local`。转换由信号(发送到状态机的事件)或计时器触发。下面的示例展示了如何定义所有三种类型的转换: @@ -715,7 +715,7 @@ public class Config3 } ``` -### [](#configuring-guards)配置守卫 +### 配置守卫 你可以使用保护来保护状态转换。你可以使用`Guard`接口来执行计算,其中方法可以访问`StateContext`。下面的示例展示了如何做到这一点: @@ -759,7 +759,7 @@ public class Config4 其次,我们使用 SPEL 表达式作为保护,要求表达式必须返回`BOOLEAN`值。在幕后,这个基于表达式的保护是`SpelExpressionGuard`。我们将其附加到状态`S2`和`S3`之间的转换。两个后卫的估值总是`true`。 -### [](#statemachine-config-actions)配置操作 +### 配置操作 你可以定义要用转换和状态执行的操作。动作总是作为源自触发器的转换的结果运行的。下面的示例展示了如何定义一个动作: @@ -848,7 +848,7 @@ public class Config52 | |用`initial()`函数定义动作,只在启动状态机或子状态时运行特定的
动作。此操作
是仅运行一次的初始化操作。如果状态机返回
并在初始和非初始状态之间向前转换,则将运行定义有
的`state()`的操作。| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -#### [](#state-actions)状态动作 +#### 状态动作 与进入和退出操作相比,运行状态操作是不同的,因为执行发生在输入状态之后,如果在特定操作完成之前发生了状态退出,则可以取消执行。 @@ -922,7 +922,7 @@ void sendEventUsingTimeout() { } ``` -#### [](#statemachine-config-transition-actions-errorhandling)转换动作错误处理 +#### 转换动作错误处理 你总是可以手动捕获异常。但是,对于为转换定义的操作,你可以定义一个错误操作,如果出现异常,则调用该操作。然后,从传递给该操作的`StateContext`中可以获得异常。下面的示例展示了如何创建处理异常的状态: @@ -985,7 +985,7 @@ public void configure(StateMachineTransitionConfigurer transitio } ``` -#### [](#statemachine-config-state-actions-errorhandling)状态动作错误处理 +#### 状态动作错误处理 与处理状态转换中的错误的逻辑类似的逻辑也可用于状态的进入和状态的退出。 @@ -1035,11 +1035,11 @@ public class Config55 } ``` -### [](#configuring-pseudo-states)配置伪状态 +### 配置伪状态 *伪态*配置通常是通过配置状态和转换来完成的。伪状态作为状态自动添加到状态机中。 -#### [](#initial-state)初始状态 +#### 初始状态 可以使用`initial()`方法将特定状态标记为初始状态。例如,这个初始操作对于初始化扩展状态变量是很好的。下面的示例展示了如何使用`initial()`方法: @@ -1073,7 +1073,7 @@ public class Config11 } ``` -#### [](#terminate-state)终止状态 +#### 终止状态 可以使用`end()`方法将特定状态标记为结束状态。对于每个子机器或区域,你最多可以执行一次。下面的示例展示了如何使用`end()`方法: @@ -1096,7 +1096,7 @@ public class Config1Enums } ``` -#### [](#state-history)国家历史 +#### 国家历史 你可以为每个单独的状态机定义一次状态历史。你需要选择其状态标识符并设置`History.SHALLOW`或`History.DEEP`。下面的示例使用`History.SHALLOW`: @@ -1136,7 +1136,7 @@ public class Config12 此外,正如前面的示例所示,你可以在同一台机器中定义从历史状态到状态顶点的缺省转换。例如,如果从未输入过机器,那么这种转换将作为默认情况发生——因此,没有可用的历史记录。如果未定义缺省状态转换,则完成对区域的正常输入。如果机器的历史记录是最终状态,也可以使用此默认转换。 -#### [](#choice-state)选择状态 +#### 选择状态 需要在状态和转换中定义选择才能正常工作。可以使用`choice()`方法将特定状态标记为选择状态。当为此选择配置转换时,此状态需要匹配源状态。 @@ -1244,7 +1244,7 @@ public class Config23 | |具有相同 API 格式的连接意味着动作可以被定义
类似。| |---|---------------------------------------------------------------------------| -#### [](#statemachine-config-states-junction)结态 +#### 结态 你需要在状态和转换两方面定义一个连接,以使其正常工作。可以使用`junction()`方法将特定状态标记为选择状态。当为此选择配置转换时,此状态需要与源状态匹配。 @@ -1308,7 +1308,7 @@ public class Config20 | |选择和连接之间的区别纯粹是学术性的,因为两者都是
用`first/then/last`结构实现的。然而,在理论上,基于 UML 建模的
,`choice`只允许一个传入转换,而`junction`允许多个传入转换。在代码级别上,
功能几乎相同。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -#### [](#fork-state)fork state +#### fork state 你必须在状态和转换中定义一个 fork,才能使其正常工作。可以使用`fork()`方法将特定状态标记为选择状态。当为此 fork 配置转换时,此状态需要匹配源状态。 @@ -1357,7 +1357,7 @@ public class Config14 } ``` -#### [](#join-state)加入状态 +#### 加入状态 你必须在状态和转换中定义一个连接,才能使其正常工作。你可以使用`join()`方法将附节状态标记为选择状态。在转换配置中,此状态不需要匹配源状态或目标状态。 @@ -1466,7 +1466,7 @@ public class Config22 } ``` -#### [](#statemachine-config-states-exitentry)出入点状态 +#### 出入点状态 你可以使用出入点和进入点来执行更多的控制出入点和进入点。下面的示例使用`withEntry`和`withExit`方法来定义入口点: @@ -1519,7 +1519,7 @@ static class Config21 extends StateMachineConfigurerAdapter { 如前面所示,你需要将特定状态标记为`exit`和`entry`状态。然后创建一个正常的转换到这些状态,并指定`withExit()`和`withEntry()`,这些状态分别在其中退出和进入。 -### [](#statemachine-config-commonsettings)配置公共设置 +### 配置公共设置 可以使用`ConfigurationConfigurer`设置公共状态机配置的一部分。有了它,你可以为状态机设置`BeanFactory`和自动启动标志。它还允许你注册`StateMachineListener`实例,配置转换冲突策略和区域执行策略。下面的示例展示了如何使用`ConfigurationConfigurer`: @@ -1616,7 +1616,7 @@ public class Config19 | |`withSecurity`、`withMonitoring`和`withPersistence`配置方法
分别在[状态机安全](#sm-security)、[监视状态机](#sm-monitoring)和[using`StateMachineRuntimePersister`](#sm-persistue-statemachinerunitemepersister)中有记载。| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#configuring-model)配置模型 +### 配置模型 `StateMachineModelFactory`是一个钩子,它允许你在不使用手动配置的情况下配置一个 Statemachine 模型。本质上,它是一种集成到配置模型中的第三方集成。你可以使用`StateMachineModelConfigurer`将`StateMachineModelFactory`连接到配置模型中。下面的示例展示了如何做到这一点: @@ -1671,7 +1671,7 @@ public static class CustomStateMachineModelFactory implements StateMachineModelF 你可以在[Eclipse 建模支持](#sm-papyrus)中找到使用此模型工厂集成的示例。你可以在[开发人员文档](#devdocs)中找到有关自定义模型集成的更多通用信息。 -### [](#statemachine-config-thingstoremember)要记住的事情 +### 要记住的事情 在从配置中定义操作、保护或任何其他引用时,需要记住 Spring Framework 是如何与 bean 一起工作的。在下一个示例中,我们定义了一个正常配置,其状态`S1`和`S2`之间有四个转换。所有转换都由`guard1`或`guard2`保护。你必须确保`guard1`是作为真实 Bean 创建的,因为它是用`@Bean`注释的,而`guard2`不是。 @@ -1732,13 +1732,13 @@ public class Config1 } ``` -## [](#sm-machineid)状态机 ID +## 状态机 ID 在方法中,各种类和接口使用`machineId`作为变量或参数。本节将更仔细地了解`machineId`与正常的机器操作和实例化之间的关系。 在运行时期间,`machineId`实际上没有任何大的操作作用,除了区分机器之间的区别——例如,在跟踪日志或进行更深入的调试时。如果没有一种简单的方法来识别这些实例,那么拥有大量不同的机器实例很快就会让开发人员迷失在翻译过程中。因此,我们添加了设置`machineId`的选项。 -### [](#using-enablestatemachine)使用`@EnableStateMachine` +### 使用`@EnableStateMachine` 在 Java 配置中将`machineId`设置为`mymachine`,然后公开日志的该值。同样的`machineId`也可以从`StateMachine.getId()`方法获得。下面的示例使用`machineId`方法: @@ -1762,7 +1762,7 @@ started S2 S1 / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine | |手动构建器(参见[通过构建器的状态机](#state-machine-via-builder))使用相同的配置
接口,这意味着行为是等效的。| |---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#using-enablestatemachinefactory)使用`@EnableStateMachineFactory` +### 使用`@EnableStateMachineFactory` 如果使用`StateMachineFactory`并使用该 ID 请求一台新机器,则可以看到相同的`machineId`正在被配置,如下例所示: @@ -1771,7 +1771,7 @@ StateMachineFactory factory = context.getBean(StateMachineFactor StateMachine machine = factory.getStateMachine("mymachine"); ``` -### [](#using-statemachinemodelfactory)使用`StateMachineModelFactory` +### 使用`StateMachineModelFactory` 在幕后,所有机器配置首先被转换为`StateMachineModel`,这样`StateMachineFactory`就不需要知道配置的起源,因为机器可以从 Java 配置、UML 或存储库构建。如果你想发疯,也可以使用自定义`StateMachineModel`,这是定义配置的最低级别。 @@ -1784,11 +1784,11 @@ FindbyMachineID`), to build different states and transitions by a `MachineID`. W | |目前,`UmlStateMachineModelFactory`不区分
不同的机器 ID,因为 UML 源总是来自相同的
文件。这种情况可能会在未来的版本中发生变化。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-factories)状态机工厂 +## 状态机工厂 在某些情况下,需要动态地创建状态机,而不是在编译时定义静态配置。例如,如果有一些定制组件使用它们自己的状态机,并且这些组件是动态创建的,那么就不可能在应用程序启动期间构建静态状态机。在内部,状态机总是通过工厂接口构建的。这就给了你一个以编程方式使用此功能的选项。状态机工厂的配置与本文档中各种示例中所示的配置完全相同,其中状态机配置是硬编码的。 -### [](#factory-through-an-adapter)通过适配器出厂 +### 通过适配器出厂 实际上,通过使用`@EnableStateMachine`创建状态机是通过工厂工作的,所以`@EnableStateMachineFactory`只是通过其接口公开了该工厂。下面的示例使用`@EnableStateMachineFactory`: @@ -1826,11 +1826,11 @@ public class Bean3 { } ``` -#### [](#adapter-factory-limitations)适配器出厂限制 +#### 适配器出厂限制 Factory 当前的限制是,它与状态机关联的所有操作和保护都共享同一个实例。这意味着,根据你的操作和保护,你需要专门处理由不同状态机调用相同 Bean 的情况。这一限制将在未来的版本中得到解决。 -### [](#state-machine-via-builder)通过构建器的状态机 +### 通过构建器的状态机 使用适配器(如上面所示)有其通过 Spring `@Configuration`类和应用程序上下文工作的要求所施加的限制。虽然这是一个配置状态机的非常清晰的模型,但它限制了编译时的配置,而这并不总是用户想要做的。如果需要构建更多的动态状态机,那么可以使用一个简单的构建器模式来构建类似的实例。通过使用字符串作为状态和事件,你可以使用此 Builder 模式在 Spring 应用程序上下文之外构建完全动态的状态机。下面的示例展示了如何做到这一点: @@ -1867,7 +1867,7 @@ StateMachine buildMachine2() throws Exception { 你需要了解何时需要将公共配置与从构建器实例化的机器一起使用。你可以使用从`withConfiguration()`返回的配置器来设置`autoStart`和`BeanFactory`。你也可以使用一个来注册`StateMachineListener`。如果通过使用`@Bean`将从构建器返回的`StateMachine`实例注册为 Bean,则`BeanFactory`将自动附加。如果在 Spring 应用程序上下文之外使用实例,则必须使用这些方法来设置所需的设施。 -## [](#sm-deferevents)使用延迟事件 +## 使用延迟事件 当发送事件时,它可能会触发`EventTrigger`,如果状态机处于成功计算触发器的状态,那么这可能会导致转换发生。通常情况下,这可能会导致一种情况,即一个事件不被接受,并被放弃。但是,你可能希望将此事件推迟到状态机进入另一种状态。在这种情况下,你可以接受该事件。换句话说,一项活动是在一个不方便的时间到来的。 @@ -1957,7 +1957,7 @@ static class Config6 extends StateMachineConfigurerAdapter { 在前面的示例中,状态机使用嵌套状态而不是平坦状态模型,因此`DEPLOY`事件可以在子状态中直接延迟。它还显示了在子状态中推迟`DONE`事件的概念,如果发送`DONE`事件时状态机恰好处于`DEPLOYPREPARE`状态,则该状态将覆盖`DEPLOY`和`DONE`状态之间的匿名转换。在`DEPLOYEXECUTE`状态下,当`DONE`事件没有延迟时,此事件将在超级状态下处理。 -## [](#sm-scopes)使用作用域 +## 使用作用域 状态机中对作用域的支持非常有限,但是你可以通过使用普通 Spring `@Scope`注释来启用`session`作用域,方法有以下两种: @@ -2061,7 +2061,7 @@ public class StateMachineController { | |Spring StateMachine POM 与 Spring MVC类没有依赖关系,你将需要使用会话范围来处理这些类。但是,如果你正在使用 Web 应用程序,那么你已经直接从 Spring MVC 或 Spring Boot 中提取了这些依赖项。| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-actions)使用动作 +## 使用动作 动作是你可以用来与状态机交互和协作的最有用的组件之一。你可以在状态机及其状态生命周期的不同位置运行操作——例如,进入或退出状态或在转换期间。下面的示例展示了如何在状态机中使用操作: @@ -2126,11 +2126,11 @@ public class SpelAction extends SpelExpressionAction { | |`StateContext`在[使用`StateContext`]中进行了描述。| |---|------------------------------------------------------------------------| -### [](#spel-expressions-with-actions)带动作的 spel 表达式 +### 带动作的 spel 表达式 你也可以使用 SPEL 表达式作为完整`Action`实现的替换。 -### [](#sm-actions-reactive)反应动作 +### 反应动作 正常的`Action`接口是一种简单的函数方法,它取`StateContext`并返回*无效*。在你阻塞方法本身之前,这里没有任何阻塞,这是一个问题,因为 Framework 无法知道它内部到底发生了什么。 @@ -2150,7 +2150,7 @@ public interface ReactiveAction extends Function, Mono< | |在内部,旧的`Action`接口用一个可运行的反应器 Mono 包装,因为它
共享相同的返回类型。我们无法控制你用那种方法做什么!| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-guards)使用保护 +## 使用保护 如[要记住的事情](#statemachine-config-thingstoremember)中所示,`guard1`和`guard2`bean 分别附加到进入和退出状态。下面的示例还对事件使用了保护: @@ -2207,11 +2207,11 @@ public class BaseGuard implements Guard { | |`StateContext`在[使用`StateContext`]节中进行了描述。| |---|--------------------------------------------------------------------------------| -### [](#spel-expressions-with-guards)带守卫的 spel 表达式 +### 带守卫的 spel 表达式 你也可以使用 SPEL 表达式作为完全保护实现的替代。唯一的要求是表达式需要返回一个`Boolean`值来满足`Guard`实现。这可以用一个`guardExpression()`函数来演示,该函数将一个表达式作为参数。 -### [](#sm-guards-reactive)反应防护 +### 反应防护 正常的`Guard`接口是一种简单的函数方法,它取`StateContext`并返回*布尔值*。在你阻塞方法本身之前,这里没有任何阻塞,这是一个问题,因为 Framework 无法知道它内部到底发生了什么。 @@ -2231,7 +2231,7 @@ public interface ReactiveGuard extends Function, Mono控制你在那个方法中做什么!| |---|----------------------------------------------------------------------------------------------------------------------------| -## [](#sm-extendedstate)使用扩展状态 +## 使用扩展状态 假设你需要创建一个状态机,该状态机跟踪用户在键盘上按下一个键的次数,然后在按键被按下 1000 次时终止。一个可能但非常幼稚的解决方案是为每 1000 次按键创建一个新的状态。你可能会突然有一个天文数字的状态,这自然是不太实际的。 @@ -2278,7 +2278,7 @@ public class ExtendedStateVariableEventListener } ``` -## [](#sm-statecontext)使用`StateContext` +## 使用`StateContext` [`StateContext`](https://DOCS. Spring.io/ Spring-StateMachine/DOCS/3.0.1/api/org/SpringFramework/StateMachine/StateContext.html)是使用状态机时最重要的对象之一,因为它被传递到各种方法和回调中,以给出状态机的当前状态以及它可能的走向。你可以将其视为当前状态机级的快照,当`StateContext`被恢复时。 @@ -2305,15 +2305,15 @@ public class ExtendedStateVariableEventListener `StateContext`被传递到各种组件中,例如`Action`和`Guard`。 -### [](#sm-statecontext-stage)阶段 +### 阶段 [`Stage`](https://DOCS. Spring.io/ Spring-stateMachine/DOCS/3.0.1/api/org/springframework/stateMachine/stateContext.stage.html)是一个`stage`状态机当前正在与用户交互的表示。当前可用的阶段是`EVENT_NOT_ACCEPTED`,`EXTENDED_STATE_CHANGED`,`STATE_CHANGED`,`STATE_ENTRY`,`STATE_EXIT`,`STATEMACHINE_ERROR`,`STATEMACHINE_START`,`STATEMACHINE_STOP`,`TRANSITION`,和`TRANSITION_END`。这些状态可能看起来很熟悉,因为它们与你可以与侦听器交互的方式相匹配(如[监听状态机事件](#sm-listeners)中所述)。 -## [](#sm-triggers)触发转换 +## 触发转换 通过使用由触发器触发的转换来驱动状态机。当前支持的触发器是`EventTrigger`和`TimerTrigger`。 -### [](#using-eventtrigger)使用`EventTrigger` +### 使用`EventTrigger` `EventTrigger`是最有用的触发器,因为它允许你通过向状态机发送事件来直接与其交互。这些事件也被称为信号。你可以在配置期间将状态与转换关联,从而将触发器添加到转换中。下面的示例展示了如何做到这一点: @@ -2372,11 +2372,11 @@ Flux> results = results.subscribe(); ``` -#### [](#sm-triggers-statemachineeventresult)statemachineeventresult +#### statemachineeventresult `StateMachineEventResult`包含有关事件发送结果的更详细信息。由此,你可以得到一个`Region`,它处理了一个事件,`Message`本身以及一个实际的`ResultType`。从`ResultType`中,你可以查看消息是否被接受、拒绝或推迟。一般来说,当下标完成时,事件被传递到机器中。 -### [](#using-timertrigger)使用`TimerTrigger` +### 使用`TimerTrigger` `TimerTrigger`当需要在没有任何用户交互的情况下自动触发某些内容时,是很有用的。`Trigger`通过在配置期间将计时器与转换关联,将其添加到转换中。 @@ -2447,13 +2447,13 @@ public class TimerAction implements Action { | |如果你希望在输入状态时发生一次延迟
之后发生某些事情,请使用`timerOnce()`。| |---|-------------------------------------------------------------------------------------------------------| -## [](#sm-listeners)监听状态机事件 +## 监听状态机事件 在某些用例中,你希望了解状态机正在发生什么,对某些事情做出反应,或者获取日志详细信息以用于调试目的。 Spring StateMachine 提供了用于添加侦听器的接口。然后,这些侦听器给出一个选项,在发生各种状态更改、操作等时获得回调。 你基本上有两种选择:监听 Spring 应用程序上下文事件或直接将监听器附加到状态机。这两者基本上提供了相同的信息。一个生成事件作为事件类,另一个通过侦听器接口生成回调。这两点都有优点和缺点,我们将在后面进行讨论。 -### [](#application-context-events)应用程序上下文事件 +### 应用程序上下文事件 应用程序上下文事件类是`OnTransitionStartEvent`,`OnTransitionEvent`,`OnTransitionEndEvent`,`OnStateExitEvent`,`OnStateEntryEvent`,`OnStateChangedEvent`,`OnStateMachineStart`,`OnStateMachineStop`,以及扩展基本事件类的其他类,`StateMachineEvent`。这些可以与 Spring `ApplicationListener`一起使用。 @@ -2503,7 +2503,7 @@ public class ManualBuilderConfig { } ``` -### [](#using-statemachinelistener)使用`StateMachineListener` +### 使用`StateMachineListener` 通过使用`StateMachineListener`,你可以扩展它并实现所有回调方法,或者使用`StateMachineListenerAdapter`类,它包含存根方法实现,并选择要覆盖的那些。下面的示例使用后一种方法: @@ -2583,7 +2583,7 @@ public class Config7 { } ``` -### [](#limitations-and-problems)限制和问题 +### 限制和问题 Spring 应用程序上下文不是那里最快的事件总线,因此我们建议对状态机发送的事件的速率给予一些考虑。为了获得更好的性能,使用`StateMachineListener`接口可能会更好。出于这个特定的原因,你可以使用带有`@EnableStateMachine`和`@EnableStateMachineFactory`的`contextEvents`标志来禁用 Spring 应用程序上下文事件,如上一节所示。下面的示例展示了如何禁用 Spring 应用程序上下文事件: @@ -2601,7 +2601,7 @@ public class Config9 } ``` -## [](#sm-context)上下文集成 +## 上下文集成 通过监听状态机的事件或使用带有状态和转换的操作来与状态机进行交互是有点限制的。这种方法有时会过于有限和冗长,无法与状态机所使用的应用程序创建交互。对于这个特定的用例,我们进行了 Spring 风格的上下文集成,可以轻松地将状态机功能插入到 bean 中。 @@ -2656,7 +2656,7 @@ public @interface WithMyBean { | |这些方法的返回类型并不重要,并且有效地
被丢弃。| |---|----------------------------------------------------------------------------------| -### [](#enabling-integration)启用集成 +### 启用集成 你可以通过使用`@EnableWithStateMachine`注释启用`@WithStateMachine`的所有特性,该注释将所需的配置导入 Spring 应用程序上下文。`@EnableStateMachine`和`@EnableStateMachineFactory`都已经使用此注释进行了注释,因此没有必要再次添加它。但是,如果一台机器是在没有配置适配器的情况下构建和配置的,则必须使用`@EnableWithStateMachine`才能使用`@WithStateMachine`的这些功能。下面的示例展示了如何做到这一点: @@ -2695,7 +2695,7 @@ static class Bean17 { | |如果机器不是作为 Bean 创建的,则需要为机器设置`BeanFactory`,如 prededing 示例中所示。否则,TGE 机器
不知道调用`@WithStateMachine`方法的处理程序。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#method-parameters)方法参数 +### 方法参数 每个注释都支持完全相同的一组可能的方法参数,但是运行时行为是不同的,这取决于注释本身和调用注释方法的阶段。要更好地理解上下文是如何工作的,请参见[使用`StateContext`]。 @@ -2738,7 +2738,7 @@ public class Bean4 { | |你可以使用`@EventHeader`,而不是使用所有带有`@EventHeaders`的事件头,它可以绑定到一个单独的头。| |---|-------------------------------------------------------------------------------------------------------------------------| -### [](#state-machine-transition-annotations)转换注释 +### 转换注释 转换的注释是`@OnTransition`、`@OnTransitionStart`和`@OnTransitionEnd`。 @@ -2798,7 +2798,7 @@ public class Bean7 { } ``` -### [](#state-annotations)状态注释 +### 状态注释 下面的状态注释是可用的:`@OnStateChanged`,`@OnStateEntry`,和`@OnStateExit`。下面的示例展示了如何使用`OnStateChanged`注释(其他两种方法的工作方式相同): @@ -2864,7 +2864,7 @@ public class Bean11 { } ``` -### [](#event-annotation)事件注释 +### 事件注释 有一个与事件相关的注释。它被命名为`@OnEventNotAccepted`。如果指定`event`属性,则可以侦听未被接受的特定事件。如果你没有指定一个事件,你可以列出未被接受的任何事件。下面的示例展示了使用`@OnEventNotAccepted`注释的两种方式: @@ -2882,7 +2882,7 @@ public class Bean12 { } ``` -### [](#state-machine-annotations)状态机注释 +### 状态机注释 以下注释可用于状态机:`@OnStateMachineStart`、`@OnStateMachineStop`和`@OnStateMachineError`。 @@ -2914,7 +2914,7 @@ public class Bean14 { } ``` -### [](#extended-state-annotation)扩展状态注释 +### 扩展状态注释 有一个扩展的状态相关注释。它被命名为[Persist](#statemachine-examples-persist)。你还可以只针对特定的`key`更改监听更改。下面的示例展示了如何使用`@OnExtendedStateChanged`,包括带`key`属性和不带`key`属性: @@ -2932,7 +2932,7 @@ public class Bean15 { } ``` -## [](#sm-accessor)使用`StateMachineAccessor` +## 使用`StateMachineAccessor` `StateMachine`是与状态机通信的主要接口。有时,你可能需要对状态机及其嵌套的机器和区域的内部结构进行更动态和程序化的访问。对于这些用例,`StateMachine`公开了一个名为`StateMachineAccessor`的功能接口,该接口提供了一个接口来访问单个`StateMachine`和`Region`实例。 @@ -2974,7 +2974,7 @@ stateMachine.getStateMachineAccessor() .withRegion().setRelay(stateMachine); ``` -## [](#sm-interceptor)使用`StateMachineInterceptor` +## 使用`StateMachineInterceptor` 你可以使用`StateMachineInterceptor`接口,而不是使用`StateMachineListener`接口。一个概念上的区别是,你可以使用拦截器来拦截和停止当前状态更改或更改其转换逻辑。你可以使用一个名为`StateMachineInterceptorAdapter`的适配器类来覆盖缺省的 no-op 方法,而不是实现一个完整的接口。 @@ -3027,7 +3027,7 @@ stateMachine.getStateMachineAccessor() | |有关前面示例中显示的错误处理的更多信息,请参见[状态机错误处理](#sm-error-handling)。| |---|--------------------------------------------------------------------------------------------------------------------| -## [](#sm-security)状态机安全 +## 状态机安全 安全功能是建立在[Spring Security](https://projects.spring.io/spring-security)的功能之上的。当需要保护状态机执行的一部分以及与状态机的交互时,安全性功能非常方便。 @@ -3041,7 +3041,7 @@ stateMachine.getStateMachineAccessor() | |有关完整的示例,请参见[Security](#statemachine-examples-security)示例。| |---|-----------------------------------------------------------------------------------| -### [](#configuring-security)配置安全性 +### 配置安全性 用于安全性的所有通用配置都在`SecurityConfigurer`中完成,它是从`StateMachineConfigurationConfigurer`中获得的。默认情况下,安全性是禁用的,即使存在 Spring 安全性类。下面的示例展示了如何启用安全性: @@ -3064,7 +3064,7 @@ static class Config4 extends StateMachineConfigurerAdapter { 如果绝对需要,可以为事件和转换自定义`AccessDecisionManager`。如果不定义决策管理器或将其设置为`null`,则默认管理器将在内部创建。 -### [](#securing-events)确保事件安全 +### 确保事件安全 事件安全性在全局级别上由`RoleVoter`定义。下面的示例展示了如何启用事件安全性: @@ -3087,7 +3087,7 @@ static class Config1 extends StateMachineConfigurerAdapter { 在前面的配置示例中,我们使用`true`的表达式,该表达式的求值总是`TRUE`。在实际应用程序中,使用始终计算为`TRUE`的表达式是没有意义的,但是它显示了表达式需要返回`TRUE`或`FALSE`的一点。我们还定义了`ROLE_ANONYMOUS`的一个属性和`ComparisonType`的一个`ANY`。有关使用属性和表达式的更多信息,请参见[使用安全属性和表达式](#sm-security-attributes-expressions)。 -### [](#securing-transitions)确保过渡 +### 确保过渡 你可以在全局范围内定义转换安全性,如下例所示。 @@ -3131,7 +3131,7 @@ static class Config2 extends StateMachineConfigurerAdapter { 有关使用属性和表达式的更多信息,请参见[使用安全属性和表达式](#sm-security-attributes-expressions)。 -### [](#securing-actions)确保操作安全 +### 确保操作安全 对于状态机中的动作没有专门的安全定义,但是你可以使用 Spring Security 中的全局方法安全性来保护动作。这要求将`Action`定义为 proxied`@Bean`,并将其`execute`方法注释为`@Secured`。下面的示例展示了如何做到这一点: @@ -3201,17 +3201,17 @@ public static class Config5 extends WebSecurityConfigurerAdapter { 有关更多详细信息,请参见 Spring 安全参考指南(可用[here](https://spring.io/projects/spring-security#learn))。 -### [](#sm-security-attributes-expressions)使用安全属性和表达式 +### 使用安全属性和表达式 通常,你可以通过两种方式定义安全属性:通过使用安全属性和通过使用安全表达式。属性更容易使用,但在功能方面相对有限。表达式提供了更多的功能,但使用起来有点困难。 -#### [](#generic-attribute-usage)泛型属性用法 +#### 泛型属性用法 默认情况下,用于事件和转换的`AccessDecisionManager`实例都使用`RoleVoter`,这意味着你可以使用 Spring Security 中的角色属性。 对于属性,我们有三种不同的比较类型:`ANY`、`ALL`和`MAJORITY`。这些比较类型映射到默认访问决策管理器(分别为`AffirmativeBased`、`UnanimousBased`和`ConsensusBased`)。如果你定义了一个自定义`S2`,那么比较类型将被有效地丢弃,因为它仅用于创建默认管理器。 -#### [](#generic-expression-usage)通用表达式用法 +#### 通用表达式用法 安全表达式必须返回`TRUE`或`FALSE`。 @@ -3234,11 +3234,11 @@ public static class Config5 extends WebSecurityConfigurerAdapter { | `hasPermission(Object target, Object permission)` |如果用户能够访问
给定权限所提供的目标——例如,`hasPermission(domainObject, 'read')`,则返回`hasPermission(domainObject, 'read')`。| |`hasPermission(Object targetId, String targetType, Object
permission)`|如果用户能够访问
给定权限所提供的目标——例如,`hasPermission(1,
'com.example.domain.Message', 'read')`,则返回。| -#### [](#event-attributes)事件属性 +#### 事件属性 你可以使用`EVENT_`的前缀来匹配事件 ID。例如,匹配事件`A`将匹配属性`EVENT_A`。 -#### [](#event-expressions)事件表达式 +#### 事件表达式 事件的表达式根对象的基类是`EventSecurityExpressionRoot`。它提供对`Message`对象的访问,该对象通过事件传递。`EventSecurityExpressionRoot`只有一种方法,下表对此进行了描述: @@ -3246,11 +3246,11 @@ public static class Config5 extends WebSecurityConfigurerAdapter { |------------------------|------------------------------------------------| |`hasEvent(Object event)`|如果事件与给定事件匹配,则返回`true`。| -#### [](#transition-attributes)转换属性 +#### 转换属性 当匹配转换源和目标时,可以分别使用`TRANSITION_SOURCE_`和`TRANSITION_TARGET_`前缀。 -#### [](#transition-expressions)转换表达式 +#### 转换表达式 用于转换的表达式根对象的基类是`DefaultWebSecurityExpressionHandler`。它提供了对`Transition`对象的访问,该对象被传递以进行转换更改。`TransitionSecurityExpressionRoot`有两个方法,下表对此进行了描述: @@ -3259,7 +3259,7 @@ public static class Config5 extends WebSecurityConfigurerAdapter { |`hasSource(Object source)`|如果转换源匹配给定源,则返回`true`。| |`hasTarget(Object target)`|如果转换目标与给定目标匹配,则返回`true`。| -### [](#sm-security-details)理解安全 +### 理解安全 本节提供了有关状态机中安全性如何工作的更详细信息。你可能真的不需要知道,但最好是透明的,而不是隐藏幕后发生的所有神奇的事情。 @@ -3274,7 +3274,7 @@ public static class Config5 extends WebSecurityConfigurerAdapter { 默认情况下,对于转换,投票人(`TransitionExpressionVoter`,`TransitionVoter`,和`RoleVoter`)被添加到
中。 -## [](#sm-error-handling)状态机错误处理 +## 状态机错误处理 如果状态机在状态转换逻辑期间检测到内部错误,则可能抛出异常。在内部处理此异常之前,你有机会进行拦截。 @@ -3398,11 +3398,11 @@ public void testActionEntryErrorWithEvent() throws Exception { | |进入/退出操作中的错误不会阻止转换发生。| |---|------------------------------------------------------------------| -## [](#sm-service)状态机服务 +## 状态机服务 StateMachine 服务是更高级别的实现,旨在提供更多的用户级功能,以简化正常的运行时操作。目前,只存在一个服务接口(`StateMachineEvent`)。 -### [](#sm-service-statemachineservice)使用`StateMachineService` +### 使用`StateMachineService` `StateMachineService`是一种接口,用于处理运行中的机器,并具有“获取”和“释放”机器的简单方法。它有一个默认的实现,名为`DefaultStateMachineService`。 @@ -3419,7 +3419,7 @@ StateMachine 服务是更高级别的实现,旨在提供更多的用户级功 在状态机中的状态更改期间,你可以使用状态机拦截器尝试将序列化状态保存到外部存储中。如果此拦截器回调失败,你可以停止状态更改尝试,然后可以手动处理此错误,而不是以不一致的状态结束。有关如何使用拦截器,请参见[using`StateMachineInterceptor`]。 -### [](#sm-persist-statemachinecontext)使用`StateMachineContext` +### 使用`StateMachineContext` 使用普通的 Java 序列化不能持久保存`StateMachine`,因为对象图太丰富,并且包含太多对其他 Spring 上下文类的依赖关系。`StateMachineContext`是状态机的运行时表示形式,你可以使用它将现有机器恢复到由特定的`StateMachineContext`对象表示的状态。 @@ -3428,7 +3428,7 @@ StateMachine 服务是更高级别的实现,旨在提供更多的用户级功 | |[多数据持久化](#statemachine-examples-datajpamultipersist)示例显示了
如何持久化并行区域。| |---|---------------------------------------------------------------------------------------------------------------------------| -### [](#sm-persist-statemachinepersister)使用`StateMachinePersister` +### 使用`StateMachinePersister` 构建`StateMachineContext`然后从它恢复状态机,如果是手动完成的,这一直是有点“黑魔法”。`StateMachinePersister`接口旨在通过提供`persist`和`restore`方法来简化这些操作。这个接口的默认实现是`DefaultStateMachinePersister`。 @@ -3512,7 +3512,7 @@ persister.restore(stateMachine2, "myid"); assertThat(stateMachine2.getState().getIds()).containsExactly("S2"); ``` -### [](#sm-persist-redis)使用 redis +### 使用 redis `RepositoryStateMachinePersist`(实现`StateMachinePersist`)为将状态机持久化到 Redis 提供了支持。具体的实现是`RedisStateMachineContextRepository`,它使用`kryo`序列化将`StateMachineContext`持久化到`Redis`。 @@ -3523,7 +3523,7 @@ assertThat(stateMachine2.getState().getIds()).containsExactly("S2"); `RedisStateMachineContextRepository`需要一个`RedisConnectionFactory`才能使其工作。我们建议对它使用`JedisConnectionFactory`,如前面的示例所示。 -### [](#sm-persist-statemachineruntimepersister)使用`StateMachineRuntimePersister` +### 使用`StateMachineRuntimePersister` `StateMachineRuntimePersister`是对`StateMachinePersist`的一个简单扩展,它添加了一个接口级方法来获得与其关联的`StateMachineInterceptor`。然后,需要此拦截器在状态更改期间持久化机器,而无需停止和启动机器。 @@ -3532,21 +3532,21 @@ assertThat(stateMachine2.getState().getIds()).containsExactly("S2"); | |有关详细用法,请参见[数据持续存在](#statemachine-examples-datapersist)示例。| |---|-------------------------------------------------------------------------------------| -## [](#sm-boot) Spring 引导支持 +## Spring 引导支持 自动配置模块(`spring-statemachine-autoconfigure`)包含与 Spring 引导集成的所有逻辑,该引导为自动配置和执行器提供了功能。你所需要的只是将这个 Spring StateMachine 库作为引导应用程序的一部分。 -### [](#sm-boot-monitoring)监视和跟踪 +### 监视和跟踪 `BootStateMachineMonitor`将自动创建并与状态机关联。`BootStateMachineMonitor`是一个自定义`StateMachineMonitor`实现,它与 Spring boot 的`MeterRegistry`和通过自定义`StateMachineTraceRepository`的端点集成。你可以通过将`spring.statemachine.monitor.enabled`键设置为`StateMachineTraceRepository`来禁用此自动配置。[Monitoring](#statemachine-examples-monitoring)示例展示了如何使用这种自动配置。 -### [](#repository-config)存储库配置 +### 存储库配置 如果从 Classpath 中找到了所需的类,则 Spring 数据存储库和实体类扫描将自动为`StateMachineTraceRepository`自动配置。 当前支持的配置是`JPA`、`Redis`和`StateMachineTraceRepository`。可以分别使用`spring.statemachine.data.jpa.repositories.enabled`、`spring.statemachine.data.redis.repositories.enabled`和`spring.statemachine.data.mongo.repositories.enabled`属性禁用存储库自动配置。 -## [](#sm-monitoring)监视状态机 +## 监视状态机 你可以使用`StateMachineMonitor`来获取更多有关转换和执行操作所需的持续时间的信息。下面的清单展示了这个接口是如何实现的。 @@ -3607,7 +3607,7 @@ public class Config1 extends StateMachineConfigurerAdapter { | |有关详细用法,请参见[Monitoring](#statemachine-examples-monitoring)示例。| |---|----------------------------------------------------------------------------------| -## [](#sm-distributed)使用分布状态 +## 使用分布状态 分布式状态可能是 Spring 状态机中最复杂的概念之一。什么是分布式状态?单个状态机中的状态很容易理解,但是,当需要通过状态机引入共享的分布式状态时,事情就变得有点复杂了。 @@ -3681,7 +3681,7 @@ public class Config 可选地,你可以将`logSize`(默认为`32`)的大小设置为状态更改的 keep 历史记录。这个设置的值必须是 2 的幂。`curatorClient`通常是一个很好的默认值。如果某个特定状态机的滞后时间超过了日志的大小,那么它就会进入错误状态,并与集合断开连接,这表明它已经失去了历史记录,也失去了完全重建同步状态的能力。 -## [](#sm-test)测试支持 +## 测试支持 我们还添加了一组实用程序类,以方便对状态机实例的测试。这些在框架中使用,但对最终用户也非常有用。 @@ -3757,7 +3757,7 @@ import static org.hamcrest.collection.IsMapContaining.hasEntry; | |预期结果的所有可能选项都记录在 Javadoc 中[`StateMachineTestPlanStepBuilder`](https://DOCS. Spring.io/ Spring-statemachine/DOCS/3.0.1/api/org/springframework/statemachine/test/statemachinetplanbuilder.statemachinetestplanstepbuilder.html)。| |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-papyrus)Eclipse 建模支持 +## Eclipse 建模支持 Eclipse Papyrus 框架支持使用 UI 建模定义状态机配置。 @@ -3791,7 +3791,7 @@ Eclipse Papyrus 框架支持使用 UI 建模定义状态机配置。 | |在打开已定义为 UML 的现有模型时,你有三个
文件:`.di`、`.notation`和`.uml`。如果模型不是在你的
Eclipse 的会话中创建的,则它不了解如何打开实际状态
图表。这是 Papyrus 插件中的一个已知问题,并且有一个简单的
解决方法。在 Papyrus 透视图中,你可以看到
你的模型的模型资源管理器。双击 Diagram Statemachine Diagram,其中
指示 Eclipse 在其正确的 Papyrus
建模插件中打开此特定模型。| |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#using-umlstatemachinemodelfactory)使用`UmlStateMachineModelFactory` +### 使用`UmlStateMachineModelFactory` 在项目中安装好 UML 文件之后,你可以使用`StateMachineModelConfigurer`将其导入到配置中,其中`StateMachineModelFactory`与模型相关联。`UmlStateMachineModelFactory`是一个特殊的工厂,它知道如何处理 Eclipse Papyrus\_ 生成的 UML 结构。源 UML 文件既可以作为 Spring `Resource`给出,也可以作为普通的位置字符串给出。下面的示例展示了如何创建`UmlStateMachineModelFactory`的实例: @@ -3820,7 +3820,7 @@ public static class Config1 extends StateMachineConfigurerAdapter匿名转换。| |---|---------------------------------------------------------------------------------| -### [](#defining-timers)定时器 +### 定时器 转换也可以基于定时事件发生。 Spring Statemachine 支持两种类型的计时器,一种是在背景上连续触发的计时器,另一种是在进入状态时延迟触发一次的计时器。 @@ -3946,7 +3946,7 @@ public static class Config2 extends StateMachineConfigurerAdapter转换。因此,其行为与选择相比纯粹是
学术性的。选择传出转换的实际逻辑完全相同。| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#defining-a-junction)定义交点 +### 定义交点 见[定义一个选择](#sm-papyrus-choice)。 -### [](#defining-entry-and-exit-points)定义进场点和出场点 +### 定义进场点和出场点 你可以使用 EntryPoint 和 ExitPoint 来创建带有子状态的状态的受控入口和出口。在下面的状态图中,事件`E1`和`E2`通过进入和退出状态`S2`而具有正常状态行为,其中正常状态行为是通过进入初始状态`S21`而发生的。 @@ -3972,23 +3972,23 @@ public static class Config2 extends StateMachineConfigurerAdapter你必须在外部定义一个 ConnectionPointReference,其入口和出口引用设置为指向一个子机引用内的正确的入口或出口点
。只有在此之后,才有可能实现
目标转换,从而正确地将
子机器引用从外部链接到内部。使用 ConnectionPointReference,你可能需要
才能从 Properties Advanced UML
Enter/Exit 中找到这些设置。UML 规范允许你定义多个条目和出口。但是,
使用状态机时,只允许使用一个状态机。| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -### [](#defining-history-states)定义历史状态 +### 定义历史状态 在研究历史国家时,有三个不同的概念在起作用。UML 定义了一段深刻的历史和一段浅薄的历史。当历史状态未知时,默认的历史状态开始起作用。以下各节对此作了说明。 -#### [](#shallow-history)浅历史 +#### 浅历史 在下面的图像中,选择了浅历史,并定义了一个过渡到它: ![莎草纸 GS18](images/papyrus-gs-18.png) -#### [](#deep-history)深历史 +#### 深历史 深度历史被用来表示具有其他深度嵌套状态的状态,从而为保存整个嵌套状态结构提供了机会。下图显示了一个使用了深层历史的定义: ![Papyrus GS19](images/papyrus-gs-19.png) -#### [](#default-history)默认历史记录 +#### 默认历史记录 如果在某个历史记录上的转换在达到其最终状态之前没有被输入,则该历史记录上的转换就会终止,那么可以使用默认的历史记录机制强制转换到特定子状态。要做到这一点,你必须定义一个转换到这个默认状态。这是从`SH`到`S22`的转换。 @@ -3996,33 +3996,33 @@ public static class Config2 extends StateMachineConfigurerAdapter,否则,当模型文件从
Classpath 复制到临时目录时,事情就会中断,这样 Eclipse 解析类就可以
读取这些文件。| |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#sm-repository)存储库支持 +## 存储库支持 本节包含与在 Spring statemachine 中使用“ Spring data repositories”有关的文档。 -### [](#sm-repository-config)存储库配置 +### 存储库配置 你可以将机器配置保留在外部存储中,可以根据需要从该存储中加载它,而不是通过使用 Java 配置或基于 UML 的配置来创建静态配置。这种集成通过 Spring 数据存储库抽象工作。 @@ -4103,7 +4103,7 @@ public static class Config3 extends StateMachineConfigurerAdapterStatemachine 项目进行外部贡献的一种很好的方式。如果你还没有准备好为
框架核心本身做出贡献,那么一个自定义的通用配方是与其他用户共享
功能的一种很好的方法。| |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#statemachine-recipes-persist)坚持 +## 坚持 持久化配方是一个简单的实用程序,它允许你使用单个状态机实例来持久化和更新存储库中任意项的状态。 @@ -4510,7 +4510,7 @@ Spring 机械是一个基础框架。也就是说,除了 Spring 框架之外 你可以在[Persist](#statemachine-examples-persist)处找到一个示例,该示例展示了如何使用此食谱。 -## [](#statemachine-recipes-tasks)任务 +## 任务 任务配方是运行使用状态机的`Runnable`实例的 DAG(有向丙烯酸图)的概念。这个配方是根据[Tasks](#statemachine-examples-tasks)样本中介绍的想法开发的。 @@ -4650,7 +4650,7 @@ TasksHandler handler = TasksHandler.builder() handler.continueFromError(); ``` -# [](#statemachine-examples)状态机示例 +# 状态机示例 引用文档的这一部分解释了状态机以及示例代码和 UML 状态图的使用。在表示状态图、 Spring StateMachine 配置和应用程序使用状态机所做的工作之间的关系时,我们使用了一些快捷方式。对于完整的示例,你应该研究样例存储库。 @@ -4705,7 +4705,7 @@ TasksHandler handler = TasksHandler.builder() | |我们在本节中引用的 JAR 的文件名是在构建这个文档的
过程中填充的,这意味着,如果你从
Master 构建示例,那么你的文件带有`BUILD-SNAPSHOT`后缀。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -## [](#statemachine-examples-turnstile)转门 +## 转门 旋转门是一种简单的设备,如果付款,它就可以让你访问。这是一个很容易用状态机建模的概念。在最简单的形式中,只有两种状态:`LOCKED`和`UNLOCKED`。两个事件,`COIN`和`PUSH`可能发生,这取决于是否有人付款或试图通过旋转栅门。下图显示了状态机: @@ -4807,7 +4807,7 @@ State changed to LOCKED Event PUSH send ``` -## [](#statemachine-examples-turnstilereactive)转门反应式 +## 转门反应式 旋转门反应是对[Turnstile](#statemachine-examples-turnstile)样本的增强,使用相同的*机械*概念,并添加一个与*机械*反应界面进行反应通信的反应 Web 层。 @@ -4911,7 +4911,7 @@ content-type: application/json ] ``` -## [](#statemachine-examples-showcase)展示 +## 展示 Showcase 是一个复杂的状态机,它显示了所有可能的转换拓扑,最多可达四个状态嵌套级别。下图显示了状态机: @@ -5227,7 +5227,7 @@ foo=0 * 注意事件`H`在不同的状态下是如何处理的(`S0`,`S1`,和`S2`)。这是一个很好的例子,说明层次结构状态及其事件处理是如何工作的。如果由于保护条件,State`S2`无法处理事件`H`,则下一步检查其父事件。这保证了,当机器处于`S2`状态时,`foo`标志始终是翻转的。然而,在`S1`状态下,事件`H`总是与其虚拟转换匹配,而没有保护或操作,因此它永远不会发生。 -## [](#statemachine-examples-cdplayer)CD 播放机 +## CD 播放机 CD 播放机是一个示例,它类似于许多人在现实世界中使用的一个用例。CD 播放机本身是一个非常简单的实体,允许用户打开一副牌,插入或更改一个磁盘,然后通过按下各种按钮来驱动播放机的功能(`eject`,`play`,`stop`,`pause`,`rewind`,以及`backward`)。 @@ -5642,7 +5642,7 @@ Greatest Hits * 我们不玩了。 -## [](#statemachine-examples-tasks)任务 +## 任务 Tasks 示例演示了区域内的并行任务处理,并添加了错误处理来自动或手动修复任务问题,然后继续回到可以再次运行任务的状态。下图显示了任务状态机: @@ -5950,7 +5950,7 @@ Entry state READY 在 precding 示例中,如果我们模拟任务`T2`或`T3`中的任一项失败,状态机将进入`MANUAL`状态,在此状态下,需要手动解决问题,然后才能返回`READY`状态。 -## [](#statemachine-examples-washer)洗衣机 +## 洗衣机 洗衣机示例演示了如何使用历史状态来恢复模拟断电情况下的运行状态配置。 @@ -6079,7 +6079,7 @@ Event RESTOREPOWER send * 状态从历史状态恢复,这将使状态机恢复到其先前已知的状态。 -## [](#statemachine-examples-persist)坚持 +## 坚持 持久化是一个示例,它使用[Persist](#statemachine-recipes-persist)配方来演示如何通过状态机控制数据库条目更新逻辑。 @@ -6249,7 +6249,7 @@ private class LocalPersistStateChangeListener implements PersistStateChangeListe } ``` -## [](#statemachine-examples-zookeeper)动物园管理员 +## 动物园管理员 ZooKeeper 是来自[Turnstile](#statemachine-examples-turnstile)示例的分布式版本。 @@ -6343,7 +6343,7 @@ sm>Exit state UNLOCKED Entry state LOCKED ``` -## [](#statemachine-examples-web)web +## web Web 是一个分布式状态机示例,它使用 ZooKeeper 状态机来处理分布式状态。见[Zookeeper](#statemachine-examples-zookeeper)。 @@ -6383,7 +6383,7 @@ Web 是一个分布式状态机示例,它使用 ZooKeeper 状态机来处理 sm dist n1 4 -## [](#statemachine-examples-scope)范围 +## 范围 范围是一个状态机示例,它使用会话范围为每个用户提供一个单独的实例。下图显示了作用域状态机中的状态和事件: @@ -6401,7 +6401,7 @@ Web 是一个分布式状态机示例,它使用 ZooKeeper 状态机来处理 sm scope 1 -## [](#statemachine-examples-security)安全性 +## 安全性 安全性是一个状态机示例,它使用了保护状态机的大多数可能组合。它确保发送事件、转换和操作的安全性。下图显示了状态机的状态和事件: @@ -6525,7 +6525,7 @@ public Action transitionAction() { 转换本身使用`ADMIN`角色进行保护,因此,如果当前用户不讨厌该角色,则此转换不会运行。 -## [](#statemachine-examples-eventservice)活动服务 +## 活动服务 事件服务示例展示了如何使用状态机概念作为事件的处理引擎。这个样本是从一个问题演变而来的: @@ -6780,7 +6780,7 @@ $ ./redis-cli sm eventservice 4 -## [](#statemachine-examples-deploy)部署 +## 部署 部署示例展示了如何使用状态机概念和 UML 建模来提供一个通用的错误处理状态。这个状态机是一个相对复杂的示例,说明了如何使用各种特性来提供一个集中的错误处理概念。下图显示了部署状态机: @@ -6820,7 +6820,7 @@ $ ./redis-cli 现在,你可以开始向机器发送事件,并选择各种消息头来驱动功能。 -## [](#statemachine-examples-ordershipping)订单运输 +## 订单运输 订单运输示例展示了如何使用状态机概念来构建一个简单的订单处理系统。 @@ -6860,7 +6860,7 @@ $ ./redis-cli ![SM OrderShipping4](images/sm-ordershipping-4.png) -## [](#statemachine-examples-datajpa) JPA 配置 +## JPA 配置 JPA 配置示例展示了如何在数据库中保存机器配置的情况下使用状态机概念。这个示例使用带有 H2 控制台的嵌入式 H2 数据库(以方便使用数据库)。 @@ -6994,7 +6994,7 @@ public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopu ] ``` -## [](#statemachine-examples-datapersist)数据持久化 +## 数据持久化 数据持久化示例展示了如何使用外部存储库中的持久化机器来实现状态机概念。这个示例使用带有 H2 控制台的嵌入式 H2 数据库(以方便使用数据库)。你还可以选择启用 Redis 或 MongoDB。 @@ -7126,7 +7126,7 @@ private synchronized StateMachine getStateMachine(String machine 如果你随后请求状态机`datajpapersist1`但没有发送任何事件,则状态机将被恢复到其持久状态`S3`。 -## [](#statemachine-examples-datajpamultipersist)数据多持久化 +## 数据多持久化 多 Ersist 样本是另外两个样本的扩展:[JPA Configuration](#statemachine-examples-datajpa)和[数据持续存在](#statemachine-examples-datapersist)。我们仍然将机器配置保存在数据库中,并将其持久化到数据库中。然而,这一次,我们也有了一个包含两个正交区域的机器,以显示这些区域是如何独立地持久化的。这个示例还使用了带有 H2 控制台的嵌入式 H2 数据库(以方便使用数据库)。 @@ -7233,7 +7233,7 @@ public static class Config extends StateMachineConfigurerAdapter ![SM DatajpamultiPersistent3](images/sm-datajpamultipersist-3.png) -## [](#statemachine-examples-datajpapersist)数据 JPA 持久化 +## 数据 JPA 持久化 数据持久化示例展示了如何使用外部存储库中的持久化机器来实现状态机概念。这个示例使用带有 H2 控制台的嵌入式 H2 数据库(以方便使用数据库)。你还可以选择启用 Redis 或 MongoDB。 @@ -7365,7 +7365,7 @@ private synchronized StateMachine getStateMachine(String machine 如果你随后请求状态机`datajpapersist1`但没有发送任何事件,则状态机将被恢复到其持久状态`S3`。 -## [](#statemachine-examples-monitoring)监测 +## 监测 监视示例展示了如何使用状态机概念来监视状态机转换和操作。下面的清单配置了我们在此示例中使用的状态机: @@ -7509,11 +7509,11 @@ public static class Config extends StateMachineConfigurerAdapter ] ``` -# [](#statemachine-faq)FAQ +# FAQ 本章回答了 Spring 机器用户最常问的问题。 -## [](#state-changes)状态变化 +## 状态变化 我怎样才能自动转到下一个州呢? @@ -7525,19 +7525,19 @@ public static class Config extends StateMachineConfigurerAdapter * 实现无触发转换,当进入状态并完成其操作时,它会自动导致状态转换到下一个状态。 -## [](#extended-state)扩展状态 +## 扩展状态 如何在状态机启动时初始化变量? 状态机中的一个重要概念是,除非触发器导致可以触发动作的状态转换,否则不会真正发生任何事情。然而,话虽如此, Spring Statemachine 总是在状态机启动时具有初始转换。通过这个初始转换,你可以运行一个简单的操作,在`StateContext`中,它可以对扩展的状态变量执行任意操作。 -# [](#appendices)附录 +# 附录 -## [](#support-content)附录 A:支持内容 +## 附录 A:支持内容 本附录提供了有关在此参考文档中使用的类和材料的通用信息。 -### [](#classes-used-in-this-document)本文档中使用的类 +### 本文档中使用的类 下面的列表显示了在整个参考指南中使用的类: @@ -7568,11 +7568,11 @@ public enum Events { } ``` -## [](#state-machine-concepts)附录 B:状态机概念 +## 附录 B:状态机概念 本附录提供了有关状态机的一般信息。 -### [](#quick-example)快速示例 +### 快速示例 假设我们有名为`STATE1`和`STATE2`的状态,以及名为`EVENT1`和`EVENT2`的事件,你可以定义状态机的逻辑,如下图所示: @@ -7652,7 +7652,7 @@ public class MyApp { } ``` -### [](#glossary)术语表 +### 术语表 **状态机** @@ -7722,37 +7722,37 @@ public class MyApp { 动作是在触发转换过程中运行的行为。 -### [](#crashcourse)状态机速成课程 +### 状态机速成课程 本附录为状态机概念提供了一个通用的速成课程。 -#### [](#states)国家 +#### 国家 状态是状态机可以使用的模型。将状态描述为一个真实世界的例子总是比试图在文档中使用抽象的概念更容易。为此,请考虑一个简单的键盘示例——我们大多数人每天都使用一个键盘。如果你有一个完整的键盘,左边有正常键,右边有数字键盘,你可能已经注意到,数字键盘可能处于两种不同的状态,这取决于 Numlock 是否被激活。如果它不是活动的,按数字垫键将导致导航使用箭头等。如果数字键盘是活动的,按下这些键将导致数字被键入。从本质上讲,键盘的数字键盘部分可以处于两种不同的状态。 要将状态概念与编程联系起来,这意味着你可以使用状态、状态变量或与状态机的另一种交互,而不是使用标志、嵌套的 if/else/break 子句或其他不切实际(有时是曲折的)逻辑。 -#### [](#pseudo-states)伪态 +#### 伪态 伪状态是一种特殊类型的状态,通常通过赋予状态一个特殊的含义(例如初始状态),将更高级的逻辑引入状态机。然后,状态机可以通过执行 UML 状态机概念中可用的各种操作,在内部对这些状态做出反应。 -##### [](#initial)首字母 +##### 首字母 对于每个状态机,总是需要**初始伪态**状态,无论你是拥有简单的一级状态机还是由子机或区域组成的更复杂的状态机。初始状态定义了状态机启动时的运行位置。没有它,状态机就不会成型。 -##### [](#end)完 +##### 完 **终止伪态**(也称为“结束状态”)表示特定状态机已达到其最终状态。实际上,这意味着状态机不再处理任何事件,也不会传输到任何其他状态。然而,在子机为区域的情况下,状态机可以从其终端状态重新启动。 -##### [](#choice)选择 +##### 选择 你可以使用**选择伪态**从这个状态中选择一个动态转换的条件分支。通过保护对动态条件进行评估,从而选择一条支路。通常使用简单的 if/elseif/else 结构来确保选择了一个分支。否则,状态机可能会陷入死锁,并且配置格式不正确。 -##### [](#junction)交点 +##### 交点 **连接伪态**在功能上与 choice 相似,因为两者都是用 if/elseif/else 结构实现的。唯一真正的区别是,连接允许多个传入转换,而选择只允许一个。因此,差异在很大程度上是学术性的,但确实有一些差异,例如,在设计状态机时使用的是真实的 UI 建模框架。 -##### [](#history)历史 +##### 历史 你可以使用**历史伪国家**来记住最近的活动状态配置。在状态机退出后,你可以使用历史状态来恢复先前已知的配置。有两种类型的历史状态可用:`SHALLOW`(只记住状态机本身的活动状态)和`DEEP`(也记住嵌套状态)。 @@ -7760,7 +7760,7 @@ public class MyApp { 如果转换在历史状态上终止,而该状态以前没有被输入(换句话说,不存在先前的历史)或它已经到达其结束状态,则转换可以通过使用默认的历史机制,强制状态机到特定子状态。此转换起源于历史状态,并终止于包含历史状态的区域的特定顶点(默认历史状态)。只有当其执行导致历史状态并且该状态以前从未处于活动状态时,才会进行此转换。否则,将执行进入该区域的正常历史记录。如果未定义缺省历史转换,则执行该区域的标准缺省条目。 -##### [](#fork)叉子 +##### 叉子 你可以使用**Fork 伪态**对一个或多个区域进行显式输入。下图显示了叉子的工作原理: @@ -7768,7 +7768,7 @@ public class MyApp { 目标状态可以是承载区域的父状态,这仅意味着区域通过输入其初始状态而被激活。你还可以直接将目标添加到区域中的任何状态,这允许更多的受控进入状态。 -##### [](#join)加入 +##### 加入 **加入伪状态**将源自不同区域的几个转换合并在一起。它通常用于等待和阻止参与区域进入其加入目标状态。下图显示了连接的工作原理: @@ -7776,53 +7776,53 @@ public class MyApp { 源状态可以是承载区域的父状态,这意味着连接状态是参与区域的终端状态。你还可以将源状态定义为区域中的任何状态,这允许受控地退出区域。 -##### [](#entry-point)切入点 +##### 切入点 **入口点伪态**表示提供状态机或状态机内部封装的状态机或复合状态的入口点。在 OWNS 入口点的状态机或复合状态的每个区域中,在该区域内最多只有一个从入口点到顶点的转换。 -##### [](#exit-point)退出点 +##### 退出点 **出口点伪状态**是状态机或复合状态的出口点,它提供了状态机或状态机内部的封装。在复合状态的任何区域(或由子机状态引用的状态机)的出口点终止的转换意味着退出该复合状态或子机状态(执行其相关的退出行为)。 -#### [](#guard-conditions)防范条件 +#### 防范条件 保护条件是根据扩展的状态变量和事件参数计算为`TRUE`或`FALSE`的表达式。保护与动作和转换一起使用,以动态地选择是否应该运行特定的动作或转换。各种防护措施、事件参数和扩展的状态变量的存在使状态机的设计更加简单。 -#### [](#events)事件 +#### 事件 事件是用于驱动状态机的最常用的触发行为。还有其他方法可以触发状态机中的行为(例如计时器),但事件才是真正让用户与状态机交互的方法。事件也被称为“信号”。它们基本上表示可能改变状态机状态的东西。 -#### [](#transitions)转换 +#### 转换 转换是源状态和目标状态之间的关系。从一种状态切换到另一种状态是由触发器引起的状态转换。 -##### [](#internal-transition)内部转换 +##### 内部转换 当需要运行一个操作而不需要引起状态转换时,使用内部转换。在内部转换中,源状态和目标状态总是相同的,并且在没有状态进入和退出动作的情况下,它与自转换相同。 -##### [](#external-versus-local-transitions)外部与局部转换 +##### 外部与局部转换 在大多数情况下,外部和局部转换在功能上是等价的,除非转换发生在超级状态和次状态之间。如果目标状态是源状态的子状态,则局部转换不会导致源状态的退出和进入。相反,如果目标是源状态的超状态,则局部转换不会导致目标状态的退出和进入。下面的图像显示了具有非常简单的超级和次状态的局部和外部转换之间的区别: statechart4 -#### [](#triggers)触发器 +#### 触发器 触发器开始转换。触发器可以由事件驱动,也可以由计时器驱动。 -#### [](#actions)动作 +#### 动作 动作实际上是将状态机状态更改粘附到用户自己的代码中。状态机可以对状态机中的各种更改和步骤(例如进入或退出状态)或进行状态转换运行操作。 动作通常具有对状态上下文的访问权限,这使运行中的代码可以选择以各种方式与状态机交互。状态上下文公开了整个状态机,因此用户可以访问扩展的状态变量、事件头(如果转换是基于事件的话),或者是一种实际的转换(在这种转换中,可以看到更详细的关于这种状态变化来自何处以及它将走向何处的信息)。 -#### [](#hierarchical-state-machines)分层状态机 +#### 分层状态机 当特定的状态必须同时存在时,层次状态机的概念被用来简化状态设计。 分层状态实际上是 UML 状态机相对于传统状态机(如 Mealy 或 Moore)的一种创新。层次结构状态允许你定义某种级别的抽象(类似于 Java 开发人员如何使用抽象类定义类结构)。例如,使用嵌套状态机,你可以在多个状态级别上定义转换(可能具有不同的条件)。状态机总是尝试查看当前状态是否能够处理事件,以及转换保护条件。如果这些条件不求值到`TRUE`,状态机只看到超级状态可以处理什么。 -#### [](#regions)区域 +#### 区域 区域(也称为正交区域)通常被视为应用于状态的排他或(异或)操作。用状态机表示的区域的概念通常有点难以理解,但是通过一个简单的示例,事情会变得简单一些。 @@ -7830,11 +7830,11 @@ public class MyApp { 将两个不同的状态机作为完全独立的实体来处理会有点不方便,因为它们仍然以某种方式一起工作。这种独立性允许正交区域在状态机的单个状态中以多个同时状态合并在一起。 -## [](#appendices-zookeeper)附录 C:分布式状态机技术论文 +## 附录 C:分布式状态机技术论文 本附录提供了关于使用 Spring Statemachine 的 ZooKeeper 实例的更详细的技术文档。 -### [](#abstract)摘要 +### 摘要 在单个 JVM 上运行的单个状态机实例之上引入“分布式状态”是一个困难而复杂的主题。“分布式状态机”的概念在简单状态机的基础上引入了一些相对复杂的问题,这是由于它的运行到完成模型,以及更普遍的是由于它的单线程执行模型,尽管正交区域可以并行运行。另一个自然的问题是,状态机转换执行是由触发器驱动的,触发器基于`event`或`timer`。 @@ -7844,7 +7844,7 @@ Spring 状态机试图通过支持分布式状态机来解决通过 JVM 边界 我们的结果表明,如果支持存储库是“CP”(讨论[later](#state-machine-technical-paper-introduction)),则分布式状态更改是一致的。我们期望我们的分布式状态机能够为需要处理共享分布式状态的应用程序提供一个基础。该模型旨在为云应用程序提供更好的方法,使其能够更容易地相互通信,而无需显式地构建这些分布式状态概念。 -### [](#state-machine-technical-paper-introduction)引言 +### 引言 Spring 状态机不强制使用单线程执行模型,因为,一旦使用了多个区域,如果应用了必要的配置,则可以并行执行区域。这是一个重要的主题,因为一旦用户想要执行并行状态机,它就会使独立区域的状态更改更快。 @@ -7870,7 +7870,7 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 `Spring Distributed Statemachine`的所有 Jepsen 测试都可以从[杰普森测试。](https://github.com/spring-projects/spring-statemachine/tree/master/jepsen/spring-statemachine-jepsen)获得 -### [](#generic-concepts)通用概念 +### 通用概念 `Distributed State Machine`的一个设计决策是,不要让每个单独的状态机实例意识到它是“分布式集成”的一部分。因为`StateMachine`的主要功能和特性可以通过其接口访问,所以将此实例包装在`DistributedStateMachine`中是有意义的,该实例拦截所有状态机通信,并与一个集成协作来协调分布式状态更改。 @@ -7878,11 +7878,11 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 正如[使用分布状态](#sm-distributed)中提到的,通过将`StateMachine`的实例包装在`DistributedStateMachine`中来启用分布式状态。具体的`StateMachineEnsemble`实现是`ZookeeperStateMachineEnsemble`提供与 ZooKeeper 的集成。 -### [](#the-role-of-zookeeperstatemachinepersist)`ZookeeperStateMachinePersist`的作用 +### `ZookeeperStateMachinePersist`的作用 我们希望有一个通用接口(`StateMachinePersist`),它可以将`StateMachineContext`持久化到任意存储中,并且`ZookeeperStateMachinePersist`为`Zookeeper`实现这个接口。 -### [](#the-role-of-zookeeperstatemachineensemble)`ZookeeperStateMachineEnsemble`的作用 +### `ZookeeperStateMachineEnsemble`的作用 虽然分布式状态机使用一组序列化的上下文来更新其自身的状态,但在使用 ZooKeeper 时,我们遇到了一个关于如何侦听这些上下文更改的概念性问题。我们可以将上下文序列化到 ZooKeeper`znode`中,并最终在`znode`数据被修改时进行监听。但是,`Zookeeper`并不能保证每次数据更改都会收到通知,因为对于`znode`已注册的`watcher`一旦触发就会被禁用,并且用户需要重新注册`watcher`。在此短时间内,`znode`数据可以被更改,从而导致事件丢失。通过以并发方式更改来自多个线程的数据,实际上很容易错过这些事件。 @@ -7890,13 +7890,13 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 循环缓冲区的大小被要求为 2 的幂,以避免整数溢出时出现麻烦。因此,我们不需要处理任何具体的案件。 -### [](#distributed-tolerance)分布式公差 +### 分布式公差 为了展示在实际生活中针对状态机的各种分布式操作是如何工作的,我们使用一组 Jepsen 测试来模拟在实际分布式集群中可能发生的各种情况。其中包括网络层面上的“大脑分裂”,多个“分布式状态机”的并行事件,以及“扩展状态变量”的变化。Jepsen 测试基于[Web](#statemachine-examples-web)示例,其中此示例实例在多个主机上运行,在运行状态机的每个节点上都有一个 ZooKeeper 实例。从本质上讲,每个状态机样本都连接到一个本地 ZooKeeper 实例,这使我们能够通过使用 Jepsen 来模拟网络条件。 本章后面所示的图形包含直接映射到状态图的状态和事件,可以在[Web](#statemachine-examples-web)中找到。 -#### [](#sm-tech-isolated-events)孤立事件 +#### 孤立事件 将一个孤立的单个事件发送到集成中的一个状态机中是最简单的测试场景,并且演示了一个状态机中的状态更改在集成中被正确地传播到其他状态机中。 @@ -7918,7 +7918,7 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 * 我们循环事件`I`,`C`,`I`,和`K`一次,通过随机节点。 -#### [](#parallel-events)并行事件 +#### 并行事件 多个分布式状态机的一个逻辑问题是,如果同一事件在完全相同的时间被发送到多个状态机,那么其中只有一个事件会导致分布式状态转换。这在某种程度上是一种预期的场景,因为能够更改分布式状态的第一个状态机(对于此事件)控制分布式转换逻辑。实际上,所有接收到相同事件的其他机器都会默默地丢弃该事件,因为分布式状态不再处于可以处理特定事件的状态。 @@ -7928,7 +7928,7 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 在前面的图像中,我们使用了与前面的示例中使用的相同的事件流([孤立事件](#sm-tech-isolated-events)),不同的是,事件总是被发送到所有节点。 -#### [](#concurrent-extended-state-variable-changes)并发扩展状态变量更改 +#### 并发扩展状态变量更改 扩展状态机变量不能保证在任何给定的时间都是原子的,但是,在分布式状态更改之后,集成中的所有状态机都应该具有同步的扩展状态。 @@ -7942,7 +7942,7 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 * 事件`J`从变量`v2`重复到`v8`,执行相同的检查。 -#### [](#partition-tolerance)分区公差 +#### 分区公差 我们需要始终假定,集群中的事情迟早会出问题,无论是 ZooKeeper 实例的崩溃、状态机的崩溃,还是“大脑分裂”之类的网络问题。(大脑分裂是指现有集群成员被隔离,因此只有部分宿主能够看到彼此的情况)。通常的情况是,大脑分裂会造成一个整体的少数和多数分割,这样在网络状态得到修复之前,少数群体中的主机无法参与一个整体。 @@ -7988,7 +7988,7 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 * 最后,将事件`K1`发送到所有状态机,以确保 Ensemble 正常工作。此状态更改将返回到状态`S21`。 -#### [](#crash-and-join-tolerance)崩溃和连接公差 +#### 崩溃和连接公差 在这个测试中,我们演示了杀死一个现有的状态机,然后将一个新实例重新加入到一个集成中,可以保持分布式状态的健康,并且新加入的状态机可以正确地同步它们的状态。下图显示了碰撞和连接公差测试: @@ -8007,11 +8007,11 @@ Spring 状态机不强制使用单线程执行模型,因为,一旦使用了 * 最后,我们做一个简单的转换,从`S211`返回`S21`,以确保所有状态机仍然正常工作。 -## [](#devdocs)开发人员文档 +## 开发人员文档 本附录为可能想要参与的开发人员或其他想要理解状态机如何工作或理解其内部概念的人员提供了通用信息。 -### [](#devdocs-configmodel)statemachine 配置模型 +### statemachine 配置模型 `StateMachineModel`和其他相关的 SPI 类是各种配置和工厂类之间的抽象。这也允许其他人更容易地集成来构建状态机。 @@ -8041,11 +8041,11 @@ ObjectStateMachineFactory factory = new ObjectStateMachineFactor StateMachine stateMachine = factory.getStateMachine(); ``` -## [](#appendix-reactormigrationguide)附录 D:反应堆迁移指南 +## 附录 D:反应堆迁移指南 `3.x`工作的主要任务是在内部和外部尽可能多地从命令式代码移动和更改到一个反应世界。这意味着一些主接口已经添加了新的 reative 方法,并且大多数内部执行 LOCIG(在适用的情况下)已经被转移到由反应堆处理。本质上,这意味着线程处理模型与`2.x`相比有很大的不同。下面几章将对所有这些变化进行回顾。 -### [](#communicating-with-a-machine)与机器通信 +### 与机器通信 我们在`StateMachine`中添加了新的反应方法,同时仍然保留了旧的阻塞事件方法。 @@ -8087,13 +8087,13 @@ machine.sendEvent(mono) boolean accepted = machine.sendEvent("EVENT"); ``` -### [](#taskexecutor-and-taskscheduler)任务执行器和任务调度程序 +### 任务执行器和任务调度程序 具有`TaskExecutor`的静态机器执行和具有`TaskScheduler`的状态动作调度已被完全取代,以利于反应堆的执行和调度。 从本质上讲,在主线程之外的执行需要在两个地方执行,首先是 *state actions*,它需要是可删除的,其次是*地区*,它应该总是独立执行的。目前,我们选择只使用*反应堆*`Schedulers.parallel()`,这应该会带来相对较好的结果,因为它试图自动使用系统中可用的 CPU 内核数量。 -### [](#reactive-examples)反应实例 +### 反应实例 虽然大多数示例仍然是相同的,但我们已经对其中的一些示例进行了全面检查,并创建了一些新的示例: -- GitLab