状态机简介
状态存储关于过去的信息,就是说:它反映从系统开始到现在时刻的输入变化。转移指示状态变更,并且用必须满足来确使转移发生的条件来描述它。动作是在给定时刻要进行的活动的描述。有多种类型的动作:
- 进入动作( entry action):在进入状态时进行
- 退出动作:在退出状态时进行
- 输入动作:依赖于当前状态和输入条件进行
- 转移动作:在进行特定转移时进行
FSM(有限状态机)可以使用上面图 1 那样的状态图(或状态转移图)来表示。此外可以使用多种类型的状态转移表。下面展示最常见的表示:当前状态(B)和条件(Y)的组合指示出下一个状态(C)。完整的动作信息可以只使用脚注来增加。包括完整动作信息的 FSM 定义可以使用状态表。
- SCXML Parser
- 核心 API,用于 SCXML 文档的解析模块,该 SCXML 文档中的各个元素解析组装成对应的 Java 对象实现。
- DataModel
- 核心 API,用于实现 SCXML 状态机中的数据模型定义,将 SCXML 文档中的 datamodel 元素对应的子元素封装成 Java 对象,供状态机引挚在后续操作中使用。
- Context and Evaluators
- 核心 API,用于实现对 SCXML 状态机中上下文环境的保存和更改,以及对 SCXML 文档中表达式语言(例如 <log expr=”${data.value}” />)的解析操作。
- Executor
- 引挚执行器的实例化模块,核心 API,通过此模块完成一个完整引挚执行器的组装和实例化,并提供引挚启动,停止服务器基础功能模块。
- Triggering Event
- 核心 API,SCXML 引挚中事件的定义实现和执行模块。此模块完成一个外部事件的封装,事件池的组织以及具体的事件作用流程控制。
- Custom Actions
- 高级 API,SCXML 状态机引挚的自定义事件支持。除了 SCXML 标准自带的 <var >、<assign> 和 <log> 等标准事件外,程序人员可以进行自定义事件的开发工作,例如系统平台的构件添加、构件删除操作等,需要程序人员的扩展开发工作。以下代码为 Custom Actions 的使用范例。
应用场景:
应用案例说明:
计时器的例子:
创建Maven工程:
创建状态机模型文件:stopwatch.xml
<dependencies>
<dependency>
<groupId>commons-scxml</groupId>
<artifactId>commons-scxml</artifactId>
<version>0.9</version>
</dependency>
<dependency>
<groupId>commons-jexl</groupId>
<artifactId>commons-jexl</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
<scxml xmlns="http://www.w3.org/2005/07/scxml"创建状态机:StopWatch.java
version="1.0"
initialstate="reset">
<state id="reset">
<transition event="watch.start" target="running"/>
</state>
<state id="running">
<transition event="watch.split" target="paused"/>
<transition event="watch.stop" target="stopped"/>
</state>
<state id="paused">
<transition event="watch.unsplit" target="running"/>
<transition event="watch.stop" target="stopped"/>
</state>
<state id="stopped">
<transition event="watch.reset" target="reset"/>
</state>
</scxml>
import org.apache.commons.scxml.SCXMLListener;
import org.apache.commons.scxml.env.AbstractStateMachine;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.model.State;
import org.apache.commons.scxml.model.Transition;
import org.apache.commons.scxml.model.TransitionTarget;
import java.util.Timer;
import java.util.TimerTask;
public class StopWatch extends AbstractStateMachine {
/**
* The events for the stop watch.
*/
public static final String EVENT_START = "watch.start",
EVENT_STOP = "watch.stop", EVENT_SPLIT = "watch.split",
EVENT_UNSPLIT = "watch.unsplit", EVENT_RESET = "watch.reset";
/**
* The fragments of the elapsed time.
*/
private int hr, min, sec, fract;
/**
* The fragments of the display time.
*/
private int dhr, dmin, dsec, dfract;
/**
* The stopwatch "split" (display freeze).
*/
private boolean split;
/**
* The Timer to keep time.
*/
private Timer timer;
/**
* The display decorations.
*/
private static final String DELIM = ":", DOT = ".", EMPTY = "", ZERO = "0";
public StopWatch() throws ModelException {
super(StopWatch.class.getClassLoader().getResource("stopwatch.xml"));
getEngine().addListener(getEngine().getStateMachine(), new EntryListener());
System.out.println(StopWatch.class.getClassLoader().getResource("stopwatch.xml"));
}
public void reset() {
hr = min = sec = fract = dhr = dmin = dsec = dfract = 0;
split = false;
}
public void running() {
split = false;
if (timer == null) {
timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
increment();
}
}, 100, 100);
}
}
public void paused() {
split = true;
}
public void stopped() {
timer.cancel();
timer = null;
}
public String getDisplay() {
String padhr = dhr > 9 ? EMPTY : ZERO;
String padmin = dmin > 9 ? EMPTY : ZERO;
String padsec = dsec > 9 ? EMPTY : ZERO;
return new StringBuffer().append(padhr).append(dhr).append(DELIM).
append(padmin).append(dmin).append(DELIM).append(padsec).
append(dsec).append(DOT).append(dfract).toString();
}
public String getCurrentState() {
return ((State)getEngine().getCurrentStatus().getStates().iterator().next()).getId();
}
private void increment() {
if (fract < 9) {
fract++;
} else {
fract = 0;
if (sec < 59) {
sec++;
} else {
sec = 0;
if (min < 59) {
min++;
} else {
min = 0;
if (hr < 99) {
hr++;
} else {
hr = 0; //wrap
}
}
}
}
if (!split) {
dhr = hr;
dmin = min;
dsec = sec;
dfract = fract;
}
}
/**
* A SCXMLListener that is only concerned about "onentry"
* notifications.
*/
protected class EntryListener implements SCXMLListener {
public void onEntry(final TransitionTarget entered) {
System.out.println("Current State:"+entered.getId());
}
public void onTransition(final TransitionTarget from,
final TransitionTarget to, final Transition transition) {
// nothing to do
}
public void onExit(final TransitionTarget exited) {
// nothing to do
}
}
}
测试代码:
public static void main(String[] args) throws ModelException {
StopWatch stopWatch = new StopWatch();
Scanner input=new Scanner(System.in);
System.out.println("event: watch.start watch.stop watch.reset watch.split watch.unsplit");
while(true){
String event=input.nextLine();
if(event.trim()!=null&&!event.trim().equals("")){
if(event.equals("exit")) break;
else{
stopWatch.fireEvent(event);
System.out.println(stopWatch.getCurrentState());
System.out.print(stopWatch.getDisplay());
}
}
}
}
每一个状态下会调用对应状态的方法来处理具体的业务处理逻辑。这样就可以实现一个简单的处理机。
Q&A:
关于序列化:2.0版本中可以通过将SCXML中attach的SCInstance实例的序列化来完成,恢复后通过deattach来恢复。
状态恢复:可以通过引入一个新的状态reset状态
reference:
http://www.w3.org/TR/scxml/