Apache Commons SCXML 0.9 常用 API 与状态机模型详解

系统梳理 Apache Commons SCXML 0.9 的 API 体系,覆盖 SCXMLExecutor 生命周期与 Status 查询、模型解析与序列化、Context 层次与 JEXL 表达式引擎、事件模型与延时派发、SCXMLListener 监听器与 In() 谓词,全部示例与回显实测通过。

Apache Commons SCXMLApache Commons SCXML2026/10/15

Apache Commons SCXML 0.9 常用 API 与状态机模型详解

目标:按「运行时 → 模型 → 上下文与表达式 → 事件 → 监听」的分层顺序,把 commons-scxml 0.9 常用 API 的职责、关键签名、配套关系与实测行为讲透;每个 API 均配可运行示例与真实回显,本文代码均已在 Ubuntu 22.04 + JDK 17 + Maven 3.9 实测通过。

commons-scxml 0.9 的 API 面可以概括为「一个核心运行时、一套模型对象、两个配套组件、三类扩展点」:SCXMLExecutor 是唯一的运行时核心;SCXML、State、Transition 等构成从 XML 反解出来的模型对象树;Context/Evaluator 是表达式求值的配套组件;EventDispatcher、SCXMLListener、ErrorReporter 是三类可注入的扩展点。本文逐一展开,安装与环境基线见第 1、2 篇,集成实战与坑位见第 3 篇,模型设计方法论见第 5 篇。

一、环境信息

  • 操作系统:Ubuntu 22.04 LTS
  • 架构:x86_64
  • JDK:Temurin 17.0.20.1(OpenJDK 1.8.0_312 同样实测通过)
  • 构建工具:Apache Maven 3.9.16
  • 组件版本:commons-scxml 0.9(2008-11-22 发布)
  • Maven 坐标:commons-scxml:commons-scxml:0.9 + 显式补充 commons-jexl:1.1、xalan:2.7.2
  • 工程目录:/opt/scxml-demo/scxml-demo(模型位于 models/,示例源码位于 src/main/java/demo/)
java -version | head -1
openjdk version "17.0.20.1" 2026-08-18

💡 技术拓展:0.9 的表达式引擎有两个实现可选,行为差异直接影响集成方案——

引擎类表达式风格环境要求本文选用
Commons JEXL 1.1env.jexl.JexlEvaluatoramount >= 100、可调用 Java 对象方法仅需 commons-jexl 1.1✅
JSP ELenv.jsp.ELEvaluator${amount >= 100}需 JSP/Servlet API(commons-el 等)❌

JEXL 路线依赖最少、可在现代 JVM 上免额外装配运行,且表达式能直接调用上下文里的 Java 对象方法(5.4 节实测),本文全部示例采用 JEXL。

二、API 总览

按包分层的 API 地图(jar tf 实测类清单整理):

层核心类职责对应章节
运行时org.apache.commons.scxml.SCXMLExecutor状态机引擎:解析产物的驱动器三
运行时org.apache.commons.scxml.Status当前状态集合与终态判定3.2
运行时org.apache.commons.scxml.SCInstance执行器内部装配体(上下文、监听注册表)5.1
模型 IOorg.apache.commons.scxml.io.SCXMLParserXML → 模型对象(SAX/Digester 实现)4.1
模型 IOorg.apache.commons.scxml.io.SCXMLSerializer模型对象 → XML(round-trip)4.2
模型 IOorg.apache.commons.scxml.io.SCXMLDigester同解析职责的旧式 Digester 入口4.1
模型org.apache.commons.scxml.model.SCXML/State/Transition 等模型对象树4.3
模型动作model.Assign/Var/Send/Log/If/ForEach 等onentry/transition 内可执行动作4.3
上下文org.apache.commons.scxml.Context/env.SimpleContext/env.jexl.JexlContext变量存储(根/局部两层)5.1
表达式org.apache.commons.scxml.Evaluator/env.jexl.JexlEvaluator表达式求值、条件求值5.2
事件org.apache.commons.scxml.TriggerEvent内外部事件对象(五种类型)6.1
事件org.apache.commons.scxml.EventDispatcher/env.SimpleDispatcher/env.SimpleScheduler外发事件派发与延时回投递6.3
监听org.apache.commons.scxml.SCXMLListener/env.Tracer/env.SimpleSCXMLListener进入/退出/转移回调七
扩展org.apache.commons.scxml.BuiltinIn() 谓词等模型内置函数5.5
便捷org.apache.commons.scxml.env.AbstractStateMachine事件即方法的薄封装基类八

三、运行时核心:SCXMLExecutor

3.1 生命周期五步

SCXMLExecutor 的完整生命周期固定为「解析 → 组装 → 挂载 → 启动 → 派发」。关键方法签名(javap 实测):

javap -cp $HOME/.m2/repository/commons-scxml/commons-scxml/0.9/commons-scxml-0.9.jar org.apache.commons.scxml.SCXMLExecutor | grep -E 'public|void go'
public org.apache.commons.scxml.Status getCurrentStatus()
public void go() throws org.apache.commons.scxml.model.ModelException
public void reset() throws org.apache.commons.scxml.model.ModelException
public void setStateMachine(org.apache.commons.scxml.model.SCXML)
public void triggerEvent(org.apache.commons.scxml.TriggerEvent)
public void triggerEvents(org.apache.commons.scxml.TriggerEvent[])
public org.apache.commons.scxml.Context getRootContext()
public void setRootContext(org.apache.commons.scxml.Context)
public void addListener(org.apache.commons.scxml.model.SCXML, org.apache.commons.scxml.SCXMLListener)
public void addListener(org.apache.commons.scxml.model.TransitionTarget, org.apache.commons.scxml.SCXMLListener)
public void addListener(org.apache.commons.scxml.model.Transition, org.apache.commons.scxml.SCXMLListener)
public void setEventdispatcher(org.apache.commons.scxml.EventDispatcher)
public void setSuperStep(boolean)
public void setRootContext(org.apache.commons.scxml.Context)

构造器三参数就是扩展点装配位:new SCXMLExecutor(evaluator, eventDispatcher, errorReporter)。最小驱动模板(源码 /opt/scxml-demo/scxml-demo/src/main/java/demo/TrafficLightDemo.java):

SCXML scxml = SCXMLParser.parse(model, new SimpleErrorHandler());
SCXMLExecutor exec = new SCXMLExecutor(new JexlEvaluator(), null, new SimpleErrorReporter());
exec.setStateMachine(scxml);
exec.go();
exec.triggerEvent(new TriggerEvent("timer", TriggerEvent.SIGNAL_EVENT));

三个装配要点:

  • eventDispatcher 传 null 时仍可同步派发事件,但不能处理 <send delay>;需要延时事件就装配 SimpleScheduler(6.3 节)。
  • go() 抛出的 ModelException 只在模型本身不合法时出现,解析阶段的 XML 错误则在 SCXMLParser.parse 处由 ErrorHandler 回调。
  • triggerEvent 同步执行:方法返回时宏步(含级联内部事件)已跑完,getCurrentStatus() 立即反映最终状态。

3.2 Status:当前状态查询

Status 是每步派发后的状态快照(javap 实测签名):

public boolean isFinal()
public java.util.Set getStates()
public java.util.Collection getEvents()
  • getStates() 返回当前活跃状态集合(TransitionTarget)。普通顺序模型只有一个元素;并行(<parallel>)或历史组合下可能有多个。
  • isFinal() 表示引擎是否已到达顶层 <final>。一个实测细节:superStep 微步模式逐级推进时该标志不即时翻转(见 3.3 节回显),判终建议同时检查 getStates()。

打印当前状态的标准写法(复用全文示例):

Set<TransitionTarget> states = exec.getCurrentStatus().getStates();
for (TransitionTarget t : states) {
    System.out.println("当前状态: " + t.getId());
}

3.3 reset 复位与 superStep 宏步

reset() 把引擎拉回初始状态并复位数据模型(订单示例实测回显,见第 3 篇 4 节):

======== 场景三: reset() 复位 ========
reset 后回到: [created], payCount=0

setSuperStep(false) 关闭宏步:每次 triggerEvents 只处理一级事件与一级内部事件,用于单步调试(模型 /opt/scxml-demo/scxml-demo/models/chain.scxml,链式内部事件,源码 demo/SuperStepDemo.java,实测回显):

---- superStep=true (默认, 宏步跑到底) ----
触发 go 后: [d], isFinal=true
---- superStep=false (每次只推进一步) ----
触发 go 后: [b], isFinal=false
空事件推进一步: [c], isFinal=false
再进一步: [d], isFinal=false

微步推进用空事件驱动:exec.triggerEvents(new TriggerEvent[] {new TriggerEvent(null, TriggerEvent.SIGNAL_EVENT)});。

四、模型解析与序列化

4.1 SCXMLParser:XML 到模型对象

public static SCXML parse(final URL scxmlURL, final ErrorHandler errHandler)
  • 入参是模型 URL 与 SAX ErrorHandler;env.SimpleErrorHandler 把解析告警打到日志后继续,errHandler 传 null 则抛出即失败。
  • 解析器按 W3C 命名空间 http://www.w3.org/2005/07/scxml 识别元素;Commons 扩展动作(如 <var>)在 http://commons.apache.org/scxml 命名空间下(第 3 篇 3.4 节实测细节)。
  • 解析成功返回 SCXML 根对象,scxml.getChildren() 即顶层状态列表:
SCXML scxml = SCXMLParser.parse(model, new SimpleErrorHandler());
System.out.println("模型解析成功, 根状态个数=" + scxml.getChildren().size());
模型解析成功, 根状态个数=3

SCXMLDigester 是同一职责的旧式入口(直接持有 Digester 实例),新代码统一用 SCXMLParser。

4.2 SCXMLSerializer:模型对象到 XML

序列化器把内存中的模型对象还原为 XML,用于模型审计、对比与落库(源码 /opt/scxml-demo/scxml-demo/src/main/java/demo/SerializerDemo.java):

SCXML scxml = SCXMLParser.parse(model, new SimpleErrorHandler());
String xml = SCXMLSerializer.serialize(scxml);
System.out.println(xml);

运行回显(实测,红绿灯模型 round-trip 结果):

解析后重新序列化的模型:
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="red">
 <state id="red">
  <transition event="timer" target="green">
  </transition>
 </state>
 <state id="green">
  <transition event="timer" target="yellow">
  </transition>
 </state>
 <state id="yellow">
  <transition event="timer" target="red">
  </transition>
 </state>
</scxml>

「解析 → 序列化」与原文件语义等价(仅缩进与自闭合风格不同),这为「外部系统改模型 → 引擎热加载」提供了往返能力。

4.3 模型对象树与内嵌动作

SCXML 树上的常用节点与内嵌动作类(全部位于 org.apache.commons.scxml.model 包):

类对应 XML读取要点
SCXML<scxml>getInitialTarget()、getChildren()、getNamespaces()
State/Parallel/Final<state>/<parallel>/<final>均实现 TransitionTarget(有 getId())
Transition<transition>getEvent()、getCond()、getTargets()、getActions()
Datamodel/Data<datamodel>/<data>SCXML.getDatamodel(),初始变量在此
Assign<assign>getName()、getExpr();要求变量已存在(先 <data> 声明)
Var<var>(Commons 命名空间)setLocal 语义,写入状态局部上下文
Send<send>getEvent()/getDelay() 均为表达式,见 6.3 节
Log/If/ElseIf/Else/ForEach对应动作元素execute() 在语义步进中被回调

onentry/onexit 通过 State.getOnEntry()/getOnExit() 访问,动作按文档顺序执行——订单示例里 paid 状态进入即执行 <assign name="payCount" expr="payCount + 1"/>,Java 侧随即读到 payCount=1(第 3 篇 4 节实测)。

五、上下文与表达式引擎

5.1 Context:根上下文与状态局部上下文

Context 接口(javap 实测签名):

public void set(java.lang.String, java.lang.Object)
public void setLocal(java.lang.String, java.lang.Object)
public java.lang.Object get(java.lang.String)
public boolean has(java.lang.String)
public java.util.Map getVars()
public void reset()

两层语义:

  • 根上下文:exec.getRootContext(),模型 <datamodel> 声明的变量、Java 侧预置的对象都住在这里;set() 按「就近已有定义」写入。
  • 状态局部上下文:每个状态进入时创建,setLocal() 只写当前层(<var> 动作即此语义),变量解析沿父链向上查找。

Java 侧读写示例(源码 demo/OrderDemo.java,实测回显):

Context ctx = exec.getRootContext();
System.out.println("根上下文初始变量: amount=" + ctx.get("amount") + ", payCount=" + ctx.get("payCount"));
ctx.set("amount", Integer.valueOf(50));
根上下文初始变量: amount=100, payCount=0
Java 侧改写变量: amount=50

Java 侧改写 amount 后,模型转移的 cond="amount >= 100" 立即按新值求值并走兜底转移——这就是模型与宿主代码共享状态的通道。

⚠️ 注意:自定义根上下文必须与表达式引擎配套,JEXL 路线用 new JexlContext();误用 SimpleContext 运行时报 SimpleContext cannot be cast to class org.apache.commons.scxml.env.jexl.JexlContext(完整报错见第 3 篇 3.2 节)。

5.2 Evaluator:表达式求值入口

Evaluator 接口四个核心方法:eval(Context, String) 求表达式值、evalCond(Context, String) 求布尔条件、evalAssign(Context, String, Object) 求赋值右侧值、newContext(Context) 创建配套子上下文。JexlEvaluator 实现委托给 Commons JEXL 1.1 的解释器,因此模型表达式可以直接使用 JEXL 语法(>、>=、==、&&、!,字符串用单引号)。

JexlEvaluator 对未定义变量的求值结果是 null 而非异常——这让 <send event="wake">(漏掉引号)这类笔误静默失效(第 3 篇 3.3 节),排查时优先怀疑表达式被当成变量名。

5.3 事件 payload 与 _eventdata

TriggerEvent 构造器的第三个参数是 payload(可序列化对象)。当前事件派发时,语义实现把 payload 暴露为上下文变量 _eventdata(多事件并存时则是 _eventdatamap,键为事件名)。模型里直接参与条件判断(模型 models/payload.scxml):

<state id="wait">
  <transition event="msg" cond="_eventdata == 'vip'" target="vipLane"/>
  <transition event="msg" target="normalLane"/>
</state>
exec.triggerEvent(new TriggerEvent("msg", TriggerEvent.SIGNAL_EVENT, "vip"));

实测回显:

---- 携带 payload='vip' 触发 msg 事件 ----
当前状态: vipLane
---- 携带 payload='basic' 触发 msg 事件 ----
当前状态: normalLane

5.4 在模型表达式中调用 Java 对象

JEXL 表达式能调用上下文中 Java 对象的公有方法——这是 0.9 与宿主代码深度协作的关键通道。把秒表 POJO 预置进根上下文(源码 demo/CalcDemo.java):

Context rootCtx = new JexlContext();
rootCtx.set("calc", new CalcDemo.Calc());

SCXMLExecutor exec = new SCXMLExecutor(evaluator, null, new SimpleErrorReporter());
exec.setRootContext(rootCtx);
exec.setStateMachine(scxml);
exec.go();
System.out.println("模型 onentry 调用 Java 对象后: sum=" + rootCtx.get("sum"));

模型侧在 onentry 里调用并落库到 <datamodel>(模型 models/calc.scxml):

<datamodel>
  <data name="sum" expr="0"/>
  <data name="grade" expr="'-'"/>
</datamodel>

<state id="start">
  <onentry>
    <assign name="sum" expr="calc.add(7, 35)"/>
    <assign name="grade" expr="calc.grade(sum)"/>
  </onentry>
  <transition event="go" target="end"/>
</state>

实测回显:

模型 onentry 调用 Java 对象后: sum=42
模型调用 Java 方法返回: grade=不及格
isFinal()=true

验证点:calc.add(7, 35) 在模型里执行并写入 sum=42;后续表达式 calc.grade(sum) 又引用了刚写入的变量,说明同一 onentry 内动作按序执行且上下文即时可见。

5.5 Builtin:模型内置谓词 In()

org.apache.commons.scxml.Builtin 提供 In(stateId) 谓词,在 cond 中判断「当前是否处于某状态」,配合层级/并行模型做跨状态守卫(模型 models/hierarchy.scxml:outer 内含 inner 与 sibling,父级转移带 In('inner') 条件):

<state id="outer" initial="inner">
  <state id="inner">
    <transition event="skip" target="sibling"/>
  </state>
  <state id="sibling"/>
  <transition event="check" cond="In('inner')" target="done"/>
</state>

源码 demo/BuiltinDemo.java 实测回显:

---- inner 激活时发 check, In('inner') 为真 ----
当前状态: done
---- 先 skip 到 sibling 再发 check, In('inner') 为假 ----
当前状态: sibling
当前状态: sibling

六、事件模型与派发

6.1 TriggerEvent:五种事件类型

public static final int SIGNAL_EVENT = 0;
public static final int CALL_EVENT = 1;
public static final int CHANGE_EVENT = 2;
public static final int TIME_EVENT = 3;
public static final int ERROR_EVENT = 4;

public TriggerEvent(final String name, final int type)
public TriggerEvent(final String name, final int type, final Object payload)
  • 应用层派发统一用 SIGNAL_EVENT;TIME_EVENT 由延时派发器产生;ERROR_EVENT 用于引擎错误通报。
  • 事件名允许点号风格(如 watch.start),只是普通字符串,与包名无关。

6.2 同步派发与事件队列

triggerEvent 是应用入口:外部事件入队后,引擎在当前调用线程内执行完整个宏步(含内部事件级联)才返回。内部事件由无延时 <send> 产生,会在同一宏步内被消费(链式模型 models/chain.scxml 一次触发直达终态,见 3.3 节回显)。批量派发用 triggerEvents(TriggerEvent[]),也是微步模式的推进入口。

6.3 EventDispatcher:外发与延时事件

EventDispatcher 接口负责 <send> 的落地,两个实现分工明确:

实现职责延时支持适用
env.SimpleDispatcher把 <send> 内容打印到日志(无外部传输通道)❌(仅登记)验证模型
env.SimpleScheduler无延时事件立即回投引擎;延时事件经内部定时器到期回投✅生产内嵌

延时事件完整链路实测(模型 models/alarm.scxml,源码 demo/SendDelayDemo.java):

<state id="idle">
  <onentry>
    <send event="'wake'" delay="'1s'"/>
  </onentry>
  <transition event="wake" target="awake"/>
</state>
exec.setEventdispatcher(new SimpleScheduler(exec));
exec.setStateMachine(scxml);
exec.go();

实测回显(毫秒数随机器浮动):

[主线程] go() 完成, 初始状态=[idle] (onentry 已登记 delay=1s 的 send)
[监听] 进入 awake, 距 go() 已 1030 ms
[主线程] 等待 1800 ms 后状态=[awake]

<send> 的 event 与 delay 都是表达式,字符串字面量必须加引号:event="'wake'"、delay="'1s'"(错误写法与报错见第 3 篇 3.3 节)。SimpleScheduler 还提供 cancel(String) 取消已登记的延时事件,对应模型 <cancel sendid="..."/> 动作。

七、监听器与调试

7.1 SCXMLListener:三个回调

public void onEntry(TransitionTarget state)
public void onExit(TransitionTarget state)
public void onTransition(TransitionTarget from, TransitionTarget to, Transition transition)

注册点有粒度差异(javap 实测重载):

  • addListener(SCXML, listener):挂根模型,覆盖全部状态——观测用首选;
  • addListener(TransitionTarget, listener):只收单个状态的通知——精准埋点用;
  • addListener(Transition, listener):只收单条转移的通知。

自定义监听器实测(源码 demo/ListenerDemo.java,完整回显):

---- 调用 go(), 观察初始进入 ----
[onEntry]      进入 red
---- 触发两次 timer, 观察回调序列 ----
[onExit]       退出 red
[onTransition] red --(timer)--> green
[onEntry]      进入 green
[onExit]       退出 green
[onTransition] green --(timer)--> yellow
[onEntry]      进入 yellow

7.2 Tracer 与官方 REPL

env.Tracer 是官方提供的监听器实现(同时充当 ErrorReporter),通过 JUL 打印 onExit/onTransition/onEntry。直接用官方 REPL 体验(实测回显):

Oct 15, 2026 2:12:31 AM org.apache.commons.scxml.env.SimpleSCXMLListener onExit
INFO: /green
Oct 15, 2026 2:12:31 AM org.apache.commons.scxml.env.SimpleSCXMLListener onTransition
INFO: transition (event = timer, cond = null, from = /green, to = /yellow)
Oct 15, 2026 2:12:31 AM org.apache.commons.scxml.env.SimpleSCXMLListener onEntry
INFO: /yellow

排障建议组合拳:模型语法问题看 SCXMLParser 的 IgnoredElementRule 日志(第 3 篇 3.4 节);流转行为问题用 Tracer 或自定义监听器;表达式问题把 SimpleErrorReporter 换成 Tracer 让 SCXMLExpressionException 打到控制台。

八、便捷基类 AbstractStateMachine 与 env 包速查

env.AbstractStateMachine 是给「一个模型对应一个 Java 门面」场景的薄封装:构造时加载模型并 go(),提供 fire(String) 派发事件、getStatus()/getStatus().getStates() 查询、resetMachine() 复位。适合把状态机封装成领域对象的小型应用;需要精细控制装配(延时事件、自定义监听)时仍直接使用 SCXMLExecutor。

org.apache.commons.scxml.env 包速查表(jar tf 实测类清单节选):

类用途
SimpleErrorHandler解析阶段容错回调
SimpleErrorReporter引擎错误收集(onError 打日志)
SimpleContext通用上下文(EL 路线或独立使用)
SimpleDispatcher<send> 日志派发器
SimpleScheduler延时事件调度器
SimpleSCXMLListener监听器空实现(按需覆盖)
Tracer监听器 + 错误上报一体
AbstractSCXMLListener监听器适配基类
AbstractStateMachine便捷门面基类
jexl.JexlEvaluator/jexl.JexlContextJEXL 表达式路线(本文推荐)
jsp.ELEvaluatorJSP EL 路线(需 JSP/Servlet API)
LogUtilscommons-logging 辅助

九、参考链接

  1. Commons SCXML 0.9 Javadoc(离线包随官方 bin 包附赠,亦可在线):https://commons.apache.org/proper/commons-scxml/apidocs/index.html
  2. Commons SCXML 用户指南:https://commons.apache.org/proper/commons-scxml/userguide.html
  3. W3C State Chart XML (SCXML) 规范:https://www.w3.org/TR/scxml/
  4. Commons JEXL 1.1 语法参考:https://commons.apache.org/proper/commons-jexl/reference/syntax.html
  5. 本专栏姊妹篇:《Apache Commons SCXML 0.9 快速入门与集成示例》《Apache Commons SCXML 0.9 状态机设计示例详解》