通过实例学习SpringStateMachine之Showcase

背景介绍

本系列通过学习SpringStateMachine中附带的10余个Sample来学习SpringStateMachine中的各个概念和用法。项目是使用的分支为2.2.0.RELEASE[1]。项目参考文档也是2.2.0.RELEASE[1]。

Showcase简介

showcase这个例子演示的是一个分层有限状态机(HFSM)[1]。该例中的状态机共分4层,并且引入了action、guard、internal transition等工具来构建分层有限状态机。同时演示了事件发生时触发action及通过guard控制action及状态转换的发生。该状态机如下图所示。

在这里插入图片描述

Showcase 依赖

项目在实现上述功能时,需要依赖springshell,官方给出的demo[4]使用了spring-shell1.2,本文将其改为spring-shell 2.0.0.RELEASE。

       <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-core</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell-starter</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

Showcase 实现

showcase这个demo使用了如下几项新功能[2](第一个demo未出现):

  • action: 用来自定义一个方法,当事件发生后会执行该方法。
  • guard: 用来自定义一个条件。当满足条件,执行转换或action否则不执行相关操作。
  • extendedState: 自定义的一些额外状态,运来进行运行时进行额外判断。
  • internal transistion: 仅用来触发action执行而不发生转换。source state和target state保持一致。

States

public enum States {
    S0, S1, S11, S12, S2, S21, S211, S212
}

Events

public enum Events {
    A, B, C, D, E, F, G, H, I
}

根据状态图,层级结构中各个状态关系如下:

S0有2个子状态S1,S2

S1有两个子状态 S11 S12
S2有一个子状态 S21

S21 有两个子状态 s211 s212
S21 有两个子状态s211和s212

如上的概念明确后,和turnStile一样,我们完成相关配置即可。这个demo只是会多处action,guard及子状态配置。

配置状态时我们通过parent方法指定一个状态的父状态。

在配置转换关系时,我们指定了guard和action。当触发事件后先要检查guard中设定的条件是否满足,如果满足才会执行状态转换及action。

通过withInternal指定了internal transition,这样事件触发仅仅因此action执行。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

@Configuration
@EnableStateMachine
public class StateMachineConfig
        extends EnumStateMachineConfigurerAdapter<States, Events> {

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                    .initial(States.S0, fooAction())
                    .state(States.S0)
                    .and()
                    .withStates()
                        .parent(States.S0)
                        .initial(States.S1)
                        .state(States.S1)
                        .and()
                        .withStates()
                            .parent(States.S1)
                            .initial(States.S11)
                            .state(States.S11)
                            .state(States.S12)
                            .and()
                    .withStates()
                        .parent(States.S0)
                        .state(States.S2)
                        .and()
                        .withStates()
                            .parent(States.S2)
                            .initial(States.S21)
                            .state(States.S21)
                            .and()
                            .withStates()
                            .parent(States.S21)
                                .initial(States.S211)
                                .state(States.S211)
                                .state(States.S212);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
                transitions
                .withExternal()
                .source(States.S1).target(States.S1).event(Events.A)
                .guard(foo1Guard())
                .and()
                .withExternal()
                .source(States.S1).target(States.S11).event(Events.B)
                .and()
                .withExternal()
                .source(States.S21).target(States.S211).event(Events.B)
                .and()
                .withExternal()
                .source(States.S1).target(States.S2).event(Events.C)
                .and()
                .withExternal()
                .source(States.S2).target(States.S1).event(Events.C)
                .and()
                .withExternal()
                .source(States.S1).target(States.S0).event(Events.D)
                .and()
                .withExternal()
                .source(States.S211).target(States.S21).event(Events.D)
                .and()
                .withExternal()
                .source(States.S0).target(States.S211).event(Events.E)
                .and()
                .withExternal()
                .source(States.S1).target(States.S211).event(Events.F)
                .and()
                .withExternal()
                .source(States.S2).target(States.S11).event(Events.F)
                .and()
                .withExternal()
                .source(States.S11).target(States.S211).event(Events.G)
                .and()
                .withExternal()
                .source(States.S211).target(States.S0).event(Events.G)
                .and()
                .withInternal()
                .source(States.S0).event(Events.H)
                .guard(foo0Guard())
                .action(fooAction())
                .and()
                .withInternal()
                .source(States.S2).event(Events.H)
                .guard(foo1Guard())
                .action(fooAction())
                .and()
                .withInternal()
                .source(States.S1).event(Events.H)
                .and()
                .withExternal()
                .source(States.S11).target(States.S12).event(Events.I)
                .and()
                .withExternal()
                .source(States.S211).target(States.S212).event(Events.I)
                .and()
                .withExternal()
                .source(States.S12).target(States.S212).event(Events.I);
    }

    @Bean
    FooAction fooAction() {
        return new FooAction();
    }

    @Bean
    public FooGuard foo0Guard() {
        return new FooGuard(0);
    }

    @Bean
    public FooGuard foo1Guard() {
        return new FooGuard(1);
    }
}

FooAction中演示了extendedState的使用,这里触发action后,我们增加了一些自定义变量。随后作为guard中的判断条件使用。

public class FooAction implements Action<States,Events> {

    private final static Log log = LogFactory.getLog(FooAction.class);

    @Override
    public void execute(StateContext<States, Events> context) {
        Map<Object, Object> variables = context.getExtendedState().getVariables();
        Integer foo = context.getExtendedState().get("foo", Integer.class);
        if (foo == null) {
            log.info("Init foo to 0");
            variables.put("foo", 0);
        } else if (foo == 0) {
            log.info("Switch foo to 1");
            variables.put("foo", 1);
        } else if (foo == 1) {
            log.info("Switch foo to 0");
            variables.put("foo", 0);
        }
    }
}

FooGuard,使用了action中在extendedState中增加的条件完成判断。

import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.guard.Guard;

/**
 * Created on 2020/2/9.
 */
public class FooGuard implements Guard<States,Events> {

    private final int match;

    public FooGuard(int match) {
        this.match = match;
    }

    @Override
    public boolean evaluate(StateContext<States, Events> context) {
        Object foo = context.getExtendedState().getVariables().get("foo");
        return (foo == null || !foo.equals(match));
    }
}

StateMachineCommands与turnStile中一致。

import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import springboot.statemachine.example.AbstractStateMachineCommands;


@ShellComponent
public class StateMachineCommands extends AbstractStateMachineCommands<States, Events> {

    @ShellMethod(key = "sm event", value = "Sends an event to a state machine")
    public String event(Events event) {
        getStateMachine().sendEvent(event);
        return "Event " + event + " send";
    }
}

AbstractStateMachineCommands也与turnStile一致完成相应的命令执行。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.util.StringUtils;

import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;


public class AbstractStateMachineCommands<S, E>{

    @Autowired
    private StateMachine<S, E> stateMachine;

    protected StateMachine<S, E> getStateMachine() {
        return stateMachine;
    }

    @ShellMethod(key = "sm state", value = "Prints current state")
    public String state() {
        State<S, E> state = stateMachine.getState();
        if (state != null) {
            return StringUtils.collectionToCommaDelimitedString(state.getIds());
        } else {
            return "No state";
        }
    }

    @ShellMethod(key = "sm start", value = "Start a state machine")
    public String start() {
        stateMachine.start();
        return "State machine started";
    }

    @ShellMethod(key = "sm stop", value = "Stop a state machine")
    public String stop() {
        stateMachine.stop();
        return "State machine stopped";
    }


    @ShellMethod(key = "sm variables", value = "Prints extended state variables")
    public String variables() {
        StringBuilder buf = new StringBuilder();
        Set<Entry<Object, Object>> entrySet = stateMachine.getExtendedState().getVariables().entrySet();
        Iterator<Entry<Object, Object>> iterator = entrySet.iterator();
        if (entrySet.size() > 0) {
            while (iterator.hasNext()) {
                Entry<Object, Object> e = iterator.next();
                buf.append(e.getKey() + "=" + e.getValue());
                if (iterator.hasNext()) {
                    buf.append("\n");
                }
            }
        } else {
            buf.append("No variables");
        }
        return buf.toString();
    }

}

总结

通过Showcase学习到了层次有限状态机(HFSM)的概念与配置。在事件触发时候使用Action来完成自定义操作,通过Guard来自定义Action和transition触发的条件,最后是通过internal transition来实现仅触发Action而不需要执行transistion的能力。

参考

[1]HFSM,https://gameinstitute.qq.com/community/detail/110991
[2]https://docs.spring.io/spring-statemachine/docs/2.2.0.RELEASE/reference/#actions

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值