通过实例学习SpringStateMachine之CD Player

2023-10-30 06:59

本文主要是介绍通过实例学习SpringStateMachine之CD Player,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景介绍

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

CD Player简介

cd player是对cd播放器的描述。cd播放器可以让用户打开光盘插槽,插入光盘,并通过按下不同的按钮来使用cd播放器的各种功能。这些按钮包括:

  • eject
  • play
  • stop
  • pause
  • rewind
  • backward

当我们打开光盘插槽,放入光盘随后按下播放按钮。那么光盘插槽会关上,机器开始播放格式。cd操作流程比较简单。我们可以将这些操作封装在一个类中,并通过boolean变量控制状态,通过嵌套if-else来控制流程。但这种方式,在操作增多时,控制会越来越复杂。因此通过状态机来进行管理。

CDPlay依然使用的是分层状态机(HFSM),状态图如下:
在这里插入图片描述

CD Player 依赖

项目在实现上述功能时,需要依赖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>

CD Player 实现

CD Player的状态如下:

public enum States {// super state of PLAYING and PAUSEDBUSY,PLAYING,PAUSED,// super state of CLOSED and OPENIDLE,CLOSED,OPEN
}

CD Player的事件如下:

public enum Events {PLAY, STOP, PAUSE, EJECT, LOAD, FORWARD, BACK
}

与前两个例子不同,CD Player有一个PLAYING状态,表示正在播放唱片,是对一个动态状态的描述。当处于这个状态时,需要不断的执行操作,来描述唱片播放的过程。直到触发其它事件,状态发生转换这个过程才停止。因此在配置状态转换时使用了定时器(timer)。每经过一定时间执行一次操作,以此来描述不断执行的播放操作。

  .source(States.PLAYING).action(playingAction()).timer(1000)

CD Player在发生状态转换(trasition)时候,附加了一些控制函数,来对播放器的状态进行修改。这个控制是通过@OnTransition与@StatesOnTransition两个注解实现。@StatesOnTransition是Demo中一个自定义注解还是依赖@OnTransition实现。当事触发,状态转换发生,转换的源或目标状态一致时,执行配置的方法。

    @OnTransition(target = "BUSY")public void busy(ExtendedState extendedState) {Object cd = extendedState.getVariables().get(Variables.CD);if (cd != null) {cdStatus = ((Cd)cd).getName();}}

接着清楚了上述的需求,得到如下的状态机配置:

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 StateMachineConfigextends EnumStateMachineConfigurerAdapter<States, Events> {@Overridepublic void configure(StateMachineStateConfigurer<States, Events> states)throws Exception {states.withStates().initial(States.IDLE).state(States.IDLE).and().withStates().parent(States.IDLE).initial(States.CLOSED).state(States.CLOSED, closedEntryAction(), null)  // 这里需要注意,使用了两个参数.state(States.OPEN).and().withStates().state(States.BUSY).and().withStates().parent(States.BUSY).initial(States.PLAYING).state(States.PLAYING).state(States.PAUSED);}@Overridepublic void configure(StateMachineTransitionConfigurer<States, Events> transitions)throws Exception {transitions.withExternal().source(States.CLOSED).target(States.OPEN).event(Events.EJECT).and().withExternal().source(States.OPEN).target(States.CLOSED).event(Events.EJECT).and().withExternal().source(States.OPEN).target(States.CLOSED).event(Events.PLAY).and().withExternal().source(States.PLAYING).target(States.PAUSED).event(Events.PAUSE).and().withInternal().source(States.PLAYING).action(playingAction()).timer(1000).and().withInternal().source(States.PLAYING).event(Events.BACK).action(trackAction()).and().withInternal().source(States.PLAYING).event(Events.FORWARD).action(trackAction()).and().withExternal().source(States.PAUSED).target(States.PLAYING).event(Events.PAUSE).and().withExternal().source(States.BUSY).target(States.IDLE).event(Events.STOP).and().withExternal().source(States.IDLE).target(States.BUSY).event(Events.PLAY).action(playAction()).guard(playGuard()).and().withInternal().source(States.OPEN).event(Events.LOAD).action(loadAction());}@Beanpublic ClosedEntryAction closedEntryAction() {return new ClosedEntryAction();}@Beanpublic LoadAction loadAction() {return new LoadAction();}@Beanpublic TrackAction trackAction() {return new TrackAction();}@Beanpublic PlayAction playAction() {return new PlayAction();}@Beanpublic PlayingAction playingAction() {return new PlayingAction();}@Beanpublic PlayGuard playGuard() {return new PlayGuard();}@Beanpublic Library library() {return Library.buildSampleLibrary();}
}

接着创建CD Player来描述播放器的各项功能。由于要在转换发生时增加一些操作,因此使用了@OnTransition注解。当事件触发,配置的转换发生。为了能让系统处理@OnTransition注解,我们用@WithStateMachine来注解CdPlayer类。

load方法实现加载曲目的功能。首先触发LOAD事件,再将CD信息作为事件参数传递。为了传递参数,通过MessageBuilder来将CD信息添加到事件头,实现参数传递。当处于LOAD状态时,执行Action,将CD信息加入到上线文ExtendedState对象中。这样后续操作就可以访问到CD信息了。

@WithStateMachine
public class CdPlayer {@Autowiredprivate StateMachine<States, Events> stateMachine;private String cdStatus = "No CD";private String trackStatus = "";public void load(Cd cd) {stateMachine.sendEvent(MessageBuilder.withPayload(Events.LOAD).setHeader(Variables.CD.toString(), cd).build());}public void play() {stateMachine.sendEvent(Events.PLAY);}public void stop() {stateMachine.sendEvent(Events.STOP);}public void pause() {stateMachine.sendEvent(Events.PAUSE);}public void eject() {stateMachine.sendEvent(Events.EJECT);}public void forward() {stateMachine.sendEvent(MessageBuilder.withPayload(Events.FORWARD).setHeader(Headers.TRACKSHIFT.toString(), 1).build());}public void back() {stateMachine.sendEvent(MessageBuilder.withPayload(Events.BACK).setHeader(Headers.TRACKSHIFT.toString(), -1).build());}public String getLdcStatus() {return cdStatus + " " + trackStatus;}//tag::snippetA[]@OnTransition(target = "BUSY")public void busy(ExtendedState extendedState) {Object cd = extendedState.getVariables().get(Variables.CD);if (cd != null) {cdStatus = ((Cd)cd).getName();}}
//end::snippetA[]@StatesOnTransition(target = States.PLAYING)public void playing(ExtendedState extendedState) {Object elapsed = extendedState.getVariables().get(Variables.ELAPSEDTIME);Object cd = extendedState.getVariables().get(Variables.CD);Object track = extendedState.getVariables().get(Variables.TRACK);if (elapsed instanceof Long && track instanceof Integer && cd instanceof Cd) {SimpleDateFormat format = new SimpleDateFormat("mm:ss");trackStatus = ((Cd) cd).getTracks()[((Integer) track)]+ " " + format.format(new Date((Long) elapsed));}}@StatesOnTransition(target = States.OPEN)public void open(ExtendedState extendedState) {cdStatus = "Open";}//tag::snippetB[]@StatesOnTransition(target = {States.CLOSED, States.IDLE})public void closed(ExtendedState extendedState) {Object cd = extendedState.getVariables().get(Variables.CD);if (cd != null) {cdStatus = ((Cd)cd).getName();} else {cdStatus = "No CD";}trackStatus = "";}
//end::snippetB[]}

接着通过Library类来描述曲库信息。

public class Library {private final List<Cd> collection;public Library(Cd[] collection) {this.collection = Arrays.asList(collection);}public List<Cd> getCollection() {return collection;}public static Library buildSampleLibrary() {Track cd1track1 = new Track("Bohemian Rhapsody", 5 * 60 + 56);Track cd1track2 = new Track("Another One Bites the Dust", 3 * 60 + 36);Cd cd1 = new Cd("Greatest Hits", new Track[] { cd1track1, cd1track2 });Track cd2track1 = new Track("A Kind of Magic", 4 * 60 + 22);Track cd2track2 = new Track("Under Pressure", 4 * 60 + 8);Cd cd2 = new Cd("Greatest Hits II", new Track[] { cd2track1, cd2track2 });return new Library(new Cd[] { cd1, cd2 });}}

最后我们创建CdPlayerCommands来接收CdPlayer的操作命令,并通过CdPlayer提供的各类方法,发送实现,执行功能。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;import java.text.SimpleDateFormat;
import java.util.Date;@ShellComponent
public class CdPlayerCommands{@Autowiredprivate CdPlayer cdPlayer;@Autowiredprivate Library library;@ShellMethod(key = "cd lcd", value = "Prints CD player lcd info")public String lcd() {return cdPlayer.getLdcStatus();}@ShellMethod(key = "cd library", value = "List user CD library")public String library() {SimpleDateFormat format = new SimpleDateFormat("mm:ss");StringBuilder buf = new StringBuilder();int i1 = 0;for (Cd cd : library.getCollection()) {buf.append(i1++ + ": " + cd.getName() + "\n");int i2 = 0;for (Track track : cd.getTracks()) {buf.append("  " + i2++ + ": " + track.getName() + "  " + format.format(new Date(track.getLength()*1000)) + "\n");}}return buf.toString();}@ShellMethod(key = "cd load", value = "Load CD into player")public String load(@ShellOption(value = {"", "index"}) int index) {StringBuilder buf = new StringBuilder();try {Cd cd = library.getCollection().get(index);cdPlayer.load(cd);buf.append("Loading cd " + cd);} catch (Exception e) {buf.append("Cd with index " + index + " not found, check library");}return buf.toString();}@ShellMethod(key = "cd play", value = "Press player play button")public void play() {cdPlayer.play();}@ShellMethod(key = "cd stop", value = "Press player stop button")public void stop() {cdPlayer.stop();}@ShellMethod(key = "cd pause", value = "Press player pause button")public void pause() {cdPlayer.pause();}@ShellMethod(key = "cd eject", value = "Press player eject button")public void eject() {cdPlayer.eject();}@ShellMethod(key = "cd forward", value = "Press player forward button")public void forward() {cdPlayer.forward();}@ShellMethod(key = "cd back", value = "Press player back button")public void back() {cdPlayer.back();}}

上述是CD Player实现涉及到的主要代码。剩余的Action,Guard对象参考Sample源码[1]对应实现即可。

总结

本例构建了CD播放器状态机。通过timer 定时触发一个action。为了在发送事件时同时发送参数,使用了MessageBuilder构建事件,这样通过设置参数头实现参数传递。最后通过注解@OnTransition在转换(transition)发生时,根据源和目标事件类型执行绑定方法的方法,实现需求功能。

参考

[1] cd player,https://github.com/spring-projects/spring-statemachine/tree/2.2.0.RELEASE/spring-statemachine-samples/cdplayer

这篇关于通过实例学习SpringStateMachine之CD Player的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/306320

相关文章

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

springboot security验证码的登录实例

《springbootsecurity验证码的登录实例》:本文主要介绍springbootsecurity验证码的登录实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录前言代码示例引入依赖定义验证码生成器定义获取验证码及认证接口测试获取验证码登录总结前言在spring

tomcat多实例部署的项目实践

《tomcat多实例部署的项目实践》Tomcat多实例是指在一台设备上运行多个Tomcat服务,这些Tomcat相互独立,本文主要介绍了tomcat多实例部署的项目实践,具有一定的参考价值,感兴趣的可... 目录1.创建项目目录,测试文China编程件2js.创建实例的安装目录3.准备实例的配置文件4.编辑实例的

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

Spring 中使用反射创建 Bean 实例的几种方式

《Spring中使用反射创建Bean实例的几种方式》文章介绍了在Spring框架中如何使用反射来创建Bean实例,包括使用Class.newInstance()、Constructor.newI... 目录1. 使用 Class.newInstance() (仅限无参构造函数):2. 使用 Construc

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析

《MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析》本文将详细讲解MyBatis-Plus中的lambdaUpdate用法,并提供丰富的案例来帮助读者更好地理解和应... 目录深入探索MyBATis-Plus中Service接口的lambdaUpdate用法及示例案例背景

MyBatis-Plus中静态工具Db的多种用法及实例分析

《MyBatis-Plus中静态工具Db的多种用法及实例分析》本文将详细讲解MyBatis-Plus中静态工具Db的各种用法,并结合具体案例进行演示和说明,具有很好的参考价值,希望对大家有所帮助,如有... 目录MyBATis-Plus中静态工具Db的多种用法及实例案例背景使用静态工具Db进行数据库操作插入

Spring中@Lazy注解的使用技巧与实例解析

《Spring中@Lazy注解的使用技巧与实例解析》@Lazy注解在Spring框架中用于延迟Bean的初始化,优化应用启动性能,它不仅适用于@Bean和@Component,还可以用于注入点,通过将... 目录一、@Lazy注解的作用(一)延迟Bean的初始化(二)与@Autowired结合使用二、实例解

前端原生js实现拖拽排课效果实例

《前端原生js实现拖拽排课效果实例》:本文主要介绍如何实现一个简单的课程表拖拽功能,通过HTML、CSS和JavaScript的配合,我们实现了课程项的拖拽、放置和显示功能,文中通过实例代码介绍的... 目录1. 效果展示2. 效果分析2.1 关键点2.2 实现方法3. 代码实现3.1 html部分3.2