Ubuntu 22.04 安装 Apache Commons SCXML 指南
在 Ubuntu 22.04 上从零安装 Apache Commons SCXML 0.9(2008 年最终版),覆盖 OpenJDK 8 与 Maven 环境、optional 依赖补齐、官方 bin 包与 jar 直跑两种方案、sha256 校验与官方交互式验证工具,命令回显均实测通过。
Ubuntu 22.04 安装 Apache Commons SCXML 指南
目标:在 Ubuntu 22.04 上从零搭建 Apache Commons SCXML 0.9 的开发与运行环境(OpenJDK 8 + Maven 3.6.3),跑通「模型解析 → 状态机驱动 → 事件流转」的最小验证程序,并给出官方 bin 包 + jar 直跑的离线方案;本文全部命令与回显均已在 Ubuntu 22.04 实测通过。
Apache Commons SCXML 是 Apache Commons 项目下的状态机引擎,实现了 W3C 的状态图 XML(State Chart XML)标准:开发者把状态、事件、转移条件写成一份 .scxml 模型文件,Java 侧用 SCXMLExecutor 加载并驱动它。它是纯 Java 库,没有独立进程与常驻服务,「安装完成」的判定标准是能解析模型并跑通一次状态流转。本文给出两种落地方式:面向常规工程的 Maven 依赖方案,以及面向无构建体系场景的官方 bin 包 + jar 直跑方案。
一、环境信息
本节先固定可复现的基线环境,后续所有命令均在此环境执行。列出操作系统、架构、JDK、构建工具与组件版本,便于读者对照复现。
- 操作系统:Ubuntu 22.04.5 LTS
- 架构:x86_64
- JDK:OpenJDK 1.8.0_504(apt 包
openjdk-8-jdk,见 4.2 节的切换说明) - 构建工具:Apache Maven 3.6.3
- 组件版本:commons-scxml 0.9(2008-11-22 发布,0.x 系列最终版本)
- 官方下载目录:
https://downloads.apache.org/commons/scxml/ - Maven 坐标:
commons-scxml:commons-scxml:0.9 - 验证用工作目录:
/opt/scxml-demo/scxml-demo(Maven 工程)、/opt/scxml-demo/jar-run(jar 直跑)
系统与架构确认(实时回显):
cat /etc/os-release | head -2
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
uname -m
x86_64
💡 说明:Commons SCXML 是库而非服务,环境中不存在需要常驻的守护进程;验证标准是解析一份
.scxml模型并驱动状态流转成功,而不是某个端口处于监听状态。
二、前置条件
本节说明安装前必须满足的条件。Ubuntu 22.04 官方源内置 openjdk-8-jdk 与 maven,联网即可获取。
- 具有 root 或 sudo 权限的 Ubuntu 22.04 系统。
- 可访问 Ubuntu 官方软件源(
apt update能正常拉取索引)。 - 可访问 Apache 官方下载站
downloads.apache.org与 Maven 中央仓库repo1.maven.org(本文实测网络直连可达)。 - 至少 2 GB 可用磁盘空间(JDK + Maven + 依赖 jar 合计约 500 MB)。
网络连通性确认(实时回显):
curl -sI --max-time 20 https://downloads.apache.org/commons/scxml/ | head -3
HTTP/1.1 200 OK
Date: Thu, 15 Oct 2026 02:22:54 GMT
Server: Apache
curl -sI --max-time 20 https://repo1.maven.org/maven2/commons-scxml/commons-scxml/0.9/commons-scxml-0.9.jar | head -1
HTTP/2 200
三、版本选型说明
本节回答三个问题:装哪个版本、它还能不能用、坐标是什么。
第一,官方在维护的最新发布版就是 0.9。 官方下载目录当前只保留 0.9 一代(实时回显):
curl -s https://downloads.apache.org/commons/scxml/ | grep -oE 'v[0-9.]+</h1>'
v0.9</h1>
发布时间可从发布说明的版本控制标识确认——$Id 行标注了 2008-11-22(实时回显):
curl -s https://downloads.apache.org/commons/scxml/RELEASE-NOTES.txt | grep 'Id:'
$Id: RELEASE-NOTES.txt 719758 2008-11-22 00:10:31Z rahul $
第二,维护状态是「休眠(Dormant)」。 Apache Commons 官网已把 SCXML 列入 Dormant 项目名单:0.9 之后 18 年再无新版本,也不再接收功能开发。这对使用者的含义是:API 完全冻结、升级无压力,但同样没有安全补丁通道;把「项目已休眠」当作选型前提而不是缺陷,是使用这个库的正确姿势——它的价值在于实现了标准化的 SCXML 模型格式,模型文件是标准 XML,引擎可以换,模型可以留着。
Maven 中央仓库的全谱系版本(实时回显):
curl -s https://repo1.maven.org/maven2/commons-scxml/commons-scxml/maven-metadata.xml | grep -E 'latest|release|version>'
<latest>0.9</latest>
<release>0.9</release>
<version>0.5</version>
<version>0.6</version>
<version>0.7</version>
<version>0.8</version>
<version>0.9</version>
| 版本线 | 最新版 | 发布时间 | Maven 坐标 | 适用场景 |
|---|---|---|---|---|
| 0.5–0.8(历史) | 0.8 | 2007 年及更早 | commons-scxml:commons-scxml:0.8 | 仅维护遗留系统时接触 |
| 0.9(最终版) | 0.9 | 2008-11-22 | commons-scxml:commons-scxml:0.9 | 本专栏全部示例所用;0.x 系列功能最全、修复最多的一版 |
第三,坐标必须写 commons-scxml 这个老 groupId。 该组件发布早于 Apache Commons 统一迁移到 org.apache.commons 的时代,中央仓库里 org.apache.commons:commons-scxml 并不存在(实时回显为 404):
curl -sI https://repo1.maven.org/maven2/org/apache/commons/commons-scxml/maven-metadata.xml | head -1
HTTP/2 404
JDK 兼容性实测结论: 0.9 的字节码为 Java 1.4 版本(major 48),本文实测 OpenJDK 1.8.0_504 与 Temurin 17.0.20.1 均能完成解析与状态流转(JDK 17 路线需按第 4.3 节补齐 optional 依赖并使用 JEXL 表达式引擎,注意点见 FAQ Q2)。本文按 OpenJDK 8 撰写安装过程,与 0.9 同代,兼容性最稳。
💡 技术拓展:Java 生态里的状态机引擎不止一家,定位差异很大——
方案 模型载体 特点 适用场景 Commons SCXML W3C SCXML 标准 XML 文件 模型与代码彻底分离,模型可热更、可跨语言复用 对模型标准化/可审计有要求的场景 Spring Statemachine Java DSL + 配置类 与 Spring 生态深度整合,有持久化/监控扩展 Spring 技术栈的新项目 Squirrel Foundation Java 注解/构建器 轻量高性能,函数式风格 嵌入式、性能敏感的纯 Java 场景 Stateless4j Java 流式 DSL 极简 API,单 jar 小型状态流转逻辑 本文采用 Commons SCXML 0.9,理由是它以 W3C SCXML 标准文件为模型载体,状态机设计成果不绑定 Java 代码;配合官方交互式验证工具(5.6 节)可以脱离编译环境直接调试模型。
四、方案 A:Maven 依赖安装(在线,推荐)
本节面向常规 Java 项目开发:apt 安装 JDK 与 Maven → 在 pom.xml 声明依赖 → 编译运行最小验证程序。
4.1 方案说明
方案 A 的链路是:apt 安装 openjdk-8-jdk 与 maven → pom 中声明 commons-scxml:commons-scxml:0.9 → Maven 自动解析传递依赖 → 编译运行验证程序。有一个 0.9 特有的前置认知必须先交代:它的 POM 把两个运行期真正需要的依赖声明成了 optional(可选依赖不会随 Maven 传递),工程里必须显式补上,否则分别栽在「表达式无法求值」和「解析器缺类」两个坑上,细节见 4.3 节与 FAQ Q1。
4.2 安装 JDK 8 与 Maven
apt-get update -qq && apt-get install -y openjdk-8-jdk maven
# 输出节选(实测首尾)
Setting up openjdk-8-jdk:amd64 (8u504-ga-1ubuntu1~22.04.3) ...
Processing triggers for libc-bin (2.35-0ubuntu3.15) ...
一个必须处理的细节:maven 包会把 default-jre(OpenJDK 11)作为依赖拉进来,安装后系统默认 java 指向 11 而不是 8。用 update-alternatives 把默认项显式切到 8(实时回显):
update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
update-alternatives --set javac /usr/lib/jvm/java-8-openjdk-amd64/bin/javac
update-alternatives: using /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java to provide /usr/bin/java (java) in manual mode
确认版本:
java -version
javac -version
openjdk version "1.8.0_504"
OpenJDK Runtime Environment (build 1.8.0_504-8u504-ga-1ubuntu1~22.04.3-b01)
OpenJDK 64-Bit Server VM (build 25.504-b01, mixed mode)
javac 1.8.0_504
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 1.8.0_504, vendor: Ubuntu, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre
验证点:mvn -version 的 Java version 一行必须显示 1.8.0_504。当机器上存在多个 JDK 时,Maven 优先读取 JAVA_HOME,所以跨会话使用建议把 export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 写入 /etc/profile.d/ 脚本。
4.3 创建工程并引入依赖
mkdir -p /opt/scxml-demo/scxml-demo/src/main/java/demo /opt/scxml-demo/scxml-demo/models
pom.xml(完整内容,文件路径 /opt/scxml-demo/scxml-demo/pom.xml):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>scxml-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<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.7.2</version>
</dependency>
</dependencies>
</project>
逐项说明关键配置:
commons-scxml:commons-scxml:0.9:引擎本体。Maven 会自动带入三个 compile 传递依赖:commons-logging:1.1.1、commons-digester:1.8、commons-beanutils:1.7.0(4.6 节依赖树可证)。commons-jexl:commons-jexl:1.1:必须显式声明。 0.9 的 POM 把 JEXL 表达式引擎标为 optional,不声明则模型里的cond、expr表达式全部无法求值。xalan:xalan:2.7.2:必须显式声明。 模型解析器用到 Xalan 的org.apache.xml.utils.PrefixResolver类,0.9 的 POM 同样把它标为 optional,不声明则解析任何模型都抛NoClassDefFoundError(完整报错与解法见 FAQ Q1)。maven.compiler.source/target=8:验证工程按 JDK 8 编译;scxml 0.9 自身是 Java 1.4 字节码,两者互不影响。
⚠️ 注意:还有一个 optional 依赖的隐性连带效应——
commons-jexl:1.1的老 POM 把junit:3.8.1声明成了 compile 依赖,会随依赖树进入你的 classpath(见 4.6 节依赖树实测),生产工程建议用<exclusions>排除它。
4.4 编写状态机模型
这是本专栏的第一个 SCXML 模型:一台红绿灯,red、green、yellow 三个状态靠 timer 事件循环流转。模型文件是纯 XML,与 Java 代码完全分离(文件路径 /opt/scxml-demo/scxml-demo/models/traffic-light.scxml):
<?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"/>
</state>
<state id="green">
<transition event="timer" target="yellow"/>
</state>
<state id="yellow">
<transition event="timer" target="red"/>
</state>
</scxml>
逐项说明:
- 根元素
<scxml>的xmlns固定为 W3C SCXML 命名空间http://www.w3.org/2005/07/scxml,解析器按该命名空间识别元素。 initial="red"指定初始状态,go()时引擎自动进入。<transition event="timer" target="green"/>表示「在当前状态收到timer事件则迁移到green」。
4.5 最小验证代码
验证程序 TrafficLightDemo.java(文件路径 /opt/scxml-demo/scxml-demo/src/main/java/demo/TrafficLightDemo.java)覆盖「解析模型 → 构建执行器 → 驱动初始态 → 连发三次事件」完整链路:
package demo;
import java.net.URL;
import java.util.Set;
import org.apache.commons.scxml.SCXMLExecutor;
import org.apache.commons.scxml.TriggerEvent;
import org.apache.commons.scxml.env.SimpleErrorHandler;
import org.apache.commons.scxml.env.SimpleErrorReporter;
import org.apache.commons.scxml.env.jexl.JexlEvaluator;
import org.apache.commons.scxml.io.SCXMLParser;
import org.apache.commons.scxml.model.SCXML;
import org.apache.commons.scxml.model.TransitionTarget;
public class TrafficLightDemo {
public static void main(String[] args) throws Exception {
URL model = new java.io.File(args[0]).toURI().toURL();
SCXML scxml = SCXMLParser.parse(model, new SimpleErrorHandler());
System.out.println("模型解析成功, 根状态个数=" + scxml.getChildren().size());
SCXMLExecutor exec = new SCXMLExecutor(new JexlEvaluator(), null, new SimpleErrorReporter());
exec.setStateMachine(scxml);
exec.go();
printStates("初始配置", exec);
exec.triggerEvent(new TriggerEvent("timer", TriggerEvent.SIGNAL_EVENT));
printStates("发出第 1 个 timer 后", exec);
exec.triggerEvent(new TriggerEvent("timer", TriggerEvent.SIGNAL_EVENT));
printStates("发出第 2 个 timer 后", exec);
exec.triggerEvent(new TriggerEvent("timer", TriggerEvent.SIGNAL_EVENT));
printStates("发出第 3 个 timer 后", exec);
}
static void printStates(String tag, SCXMLExecutor exec) {
Set<TransitionTarget> states = exec.getCurrentStatus().getStates();
for (TransitionTarget t : states) {
System.out.println(tag + " -> " + t.getId());
}
}
}
核心五步:SCXMLParser.parse() 把 XML 解析成 SCXML 模型对象;new SCXMLExecutor(evaluator, null, errorReporter) 组装执行器,表达式引擎选 JexlEvaluator;setStateMachine() 挂载模型;go() 驱动引擎进入初始状态;triggerEvent() 派发事件并立即完成转移。
4.6 编译与运行验证
cd /opt/scxml-demo/scxml-demo && mvn -B clean compile 2>&1 | tail -3
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
确认依赖树中三组依赖各就各位(输出节选,实测):
mvn -B dependency:tree 2>&1 | grep -E ':tree|\\\\-|\\+-'
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ scxml-demo ---
[INFO] +- commons-scxml:commons-scxml:jar:0.9:compile
[INFO] | +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] | +- commons-digester:commons-digester:1.8:compile
[INFO] | \- commons-beanutils:commons-beanutils:jar:1.7.0:compile
[INFO] +- commons-jexl:commons-jexl:jar:1.1:compile
[INFO] | \- junit:junit:jar:3.8.1:compile
[INFO] \- xalan:xalan:jar:2.7.2:compile
[INFO] \- xalan:serializer:jar:2.7.2:compile
[INFO] \- xml-apis:xml-apis:jar:1.3.04:compile
依赖树证明了 4.3 节的两个论断:commons-logging、commons-digester、commons-beanutils 自动传递进来,而 commons-jexl 与 xalan 因 optional 声明必须手工补齐;junit:3.8.1 混入 compile scope 的问题也在此可见。
编译产物的 classpath 需要把六个 jar 全部拼上,运行验证程序:
R=~/.m2/repository
CP=target/classes:$R/commons-scxml/commons-scxml/0.9/commons-scxml-0.9.jar:$R/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar:$R/commons-digester/commons-digester/1.8/commons-digester-1.8.jar:$R/commons-beanutils/commons-beanutils/1.7.0/commons-beanutils-1.7.0.jar:$R/commons-jexl/commons-jexl/1.1/commons-jexl-1.1.jar:$R/xalan/xalan/2.7.2/xalan-2.7.2.jar
java -Dfile.encoding=UTF-8 -cp $CP demo.TrafficLightDemo /opt/scxml-demo/scxml-demo/models/traffic-light.scxml
模型解析成功, 根状态个数=3
初始配置 -> red
发出第 1 个 timer 后 -> green
发出第 2 个 timer 后 -> yellow
发出第 3 个 timer 后 -> red
结果解读:初始配置为 red(与模型 initial 属性一致);三次 timer 事件使状态沿 red → green → yellow → red 循环一周回到起点,转移逻辑与模型声明完全吻合。至此 commons-scxml 0.9 在本机完整可用。
验证点汇总:
| 检查项 | 预期(实测值) | 不符时排查 |
|---|---|---|
mvn clean compile | BUILD SUCCESS | 报缺类 → FAQ Q1;报找不到符号 → 检查源码目录 src/main/java/demo |
| 依赖树 compile 节点 | 6 个 jar(含 serializer、xml-apis 传递节点) | 缺 jexl/xalan → 4.3 节 pom 漏抄 |
| 运行首行 | 模型解析成功, 根状态个数=3 | NoClassDefFoundError: PrefixResolver → FAQ Q1 |
| 运行末行 | 发出第 3 个 timer 后 -> red | 中文乱码 → FAQ Q3 |
五、方案 B:官方 bin 包 + jar 直跑(离线友好)
本节面向没有构建体系的场景:用官方发布的二进制包拿到主 jar,再从中央仓库补齐 5 个依赖 jar,全程 javac + java 完成。
5.1 方案说明
链路是:下载官方 bin 包 → 校验 sha256 → 解压得到 commons-scxml-0.9.jar → 收集依赖 jar → javac -cp 编译 → java -cp 运行。与近年的 Commons 组件不同,0.9 的 bin 包不捆绑依赖 jar(实测包内只有主 jar、javadoc、sources 与文档),依赖必须单独收集,这正是老版本交付形态的特点。
5.2 下载与完整性校验
cd /opt/scxml-demo
curl -sO --max-time 120 https://downloads.apache.org/commons/scxml/binaries/commons-scxml-0.9-bin.tar.gz
curl -sO --max-time 60 https://downloads.apache.org/commons/scxml/binaries/commons-scxml-0.9-bin.tar.gz.sha256
ls -l commons-scxml-0.9-bin.tar.gz
-rw-r--r-- 1 root root 1733016 Oct 15 09:56 commons-scxml-0.9-bin.tar.gz
校验 sha256(期望值与实测值必须逐字一致):
echo "期望: $(cut -d' ' -f1 commons-scxml-0.9-bin.tar.gz.sha256)"
echo "实测: $(sha256sum commons-scxml-0.9-bin.tar.gz | awk '{print $1}')"
期望: 887c8922a1b7a06452d7a0b1aa97e09b719340bc7f5fc57efe5324fe84f2708d
实测: 887c8922a1b7a06452d7a0b1aa97e09b719340bc7f5fc57efe5324fe84f2708d
⚠️ 注意:官方
.sha256文件内含*./scxml/binaries/路径前缀,直接用sha256sum -c会因路径不匹配而失败,按本文方式取出第一列哈希值人工比对即可。
5.3 解压
tar xzf commons-scxml-0.9-bin.tar.gz && ls commons-scxml-0.9/
apidocs
commons-scxml-0.9.jar
commons-scxml-0.9-javadoc.jar
commons-scxml-0.9-sources.jar
LICENSE.txt
NOTICE.txt
RELEASE-NOTES.txt
主 jar 为 commons-scxml-0.9.jar(175862 字节,文件日期即发布日,实测):
ls -l commons-scxml-0.9/commons-scxml-0.9.jar
-rw-r--r-- 1 root root 175862 Nov 22 2008 commons-scxml-0.9/commons-scxml-0.9.jar
包内附带的 apidocs/ 目录是完整 Javadoc 站点,离线环境可直接用浏览器查阅 API,这是 0.9 交付包的一个意外之喜。
5.4 收集依赖 jar
从 Maven 中央仓库补齐 5 个依赖(与 4.6 节依赖树一一对应):
cd /opt/scxml-demo/jar-run && mkdir -p lib
cp /opt/scxml-demo/commons-scxml-0.9/commons-scxml-0.9.jar lib/
cd lib
curl -sO https://repo1.maven.org/maven2/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar
curl -sO https://repo1.maven.org/maven2/commons-digester/commons-digester/1.8/commons-digester-1.8.jar
curl -sO https://repo1.maven.org/maven2/commons-beanutils/commons-beanutils/1.7.0/commons-beanutils-1.7.0.jar
curl -sO https://repo1.maven.org/maven2/commons-jexl/commons-jexl/1.1/commons-jexl-1.1.jar
curl -sO https://repo1.maven.org/maven2/xalan/xalan/2.7.2/xalan-2.7.2.jar
ls -l | awk '{print $5, $9}'
188671 commons-beanutils-1.7.0.jar
143602 commons-digester-1.8.jar
132165 commons-jexl-1.1.jar
60686 commons-logging-1.1.1.jar
175862 commons-scxml-0.9.jar
3154938 xalan-2.7.2.jar
5.5 javac 编译与 java 运行
把 4.5 节的验证源码复制到直跑目录,用 6 个 jar 作为 classpath 编译运行:
cd /opt/scxml-demo/jar-run
cp /opt/scxml-demo/scxml-demo/src/main/java/demo/TrafficLightDemo.java .
mkdir -p out
javac -encoding UTF-8 -cp lib/commons-scxml-0.9.jar:lib/commons-logging-1.1.1.jar:lib/commons-digester-1.8.jar:lib/commons-beanutils-1.7.0.jar:lib/commons-jexl-1.1.jar:lib/xalan-2.7.2.jar -d out TrafficLightDemo.java
Note: TrafficLightDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Note 只是泛型原始类型提示(0.9 的 getStates() 返回裸 Set),不影响使用。注意类带 demo 包名,运行必须写全限定类名 demo.TrafficLightDemo:
java -Dfile.encoding=UTF-8 -cp out:lib/commons-scxml-0.9.jar:lib/commons-logging-1.1.1.jar:lib/commons-digester-1.8.jar:lib/commons-beanutils-1.7.0.jar:lib/commons-jexl-1.1.jar:lib/xalan-2.7.2.jar demo.TrafficLightDemo /opt/scxml-demo/scxml-demo/models/traffic-light.scxml
模型解析成功, 根状态个数=3
初始配置 -> red
发出第 1 个 timer 后 -> green
发出第 2 个 timer 后 -> yellow
发出第 3 个 timer 后 -> red
验证点:与方案 A 的输出逐行一致,jar 直跑链路可用。
5.6 官方交互式验证工具(免编译调试模型)
主 jar 内置了一个交互式 REPL:org.apache.commons.scxml.test.StandaloneJexlExpressions。它加载模型后打印序列化结果,随后从标准输入读事件(一行一个事件,多个事件用空格分隔,quit 退出),实时通过 Tracer 打印进入/退出/转移回调。用它可以在完全不写 Java 代码的情况下验证模型(实时回显,事件 timer 发两次后退出):
cd /opt/scxml-demo
CP=commons-scxml-0.9/commons-scxml-0.9.jar:jar-run/lib/commons-logging-1.1.1.jar:jar-run/lib/commons-digester-1.8.jar:jar-run/lib/commons-beanutils-1.7.0.jar:jar-run/lib/commons-jexl-1.1.jar:jar-run/lib/xalan-2.7.2.jar
printf 'timer\ntimer\nquit\n' | java -cp $CP org.apache.commons.scxml.test.StandaloneJexlExpressions scxml-demo/models/traffic-light.scxml
# 输出节选(实测首尾)
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="red">
<!-- http://commons.apache.org/scxml -->
<state id="red">
<transition event="timer" target="green">
</transition>
</state>
(模型序列化主体与首次事件回调输出已按实测首尾节选截断)
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
工具先回显模型序列化结果,再以 JUL 日志格式打印每次状态变化(from = /red, to = /green 等),连发两次 timer 后状态停在 /yellow,与 4.6 节 Java 驱动的结果吻合。
六、方案对比与选择建议
| 维度 | 方案 A:Maven 依赖 | 方案 B:官方 bin 包 + jar 直跑 |
|---|---|---|
| 依赖装配 | 6 个 jar 自动解析,含传递依赖 | 手工收集 5 个依赖 jar |
| 是否联网 | 首次构建需联网 | 仅下载 jar 时需要联网 |
| 版本管控 | pom 统一声明,可锁定 | 靠文件名区分,无锁 |
| 模型调试 | 需写驱动代码 | 可用官方 REPL 工具直接联调 |
| 适用场景 | 常规工程开发 | 离线服务器、遗留系统、纯模型验证 |
选型建议:有构建体系一律方案 A;临时验证模型或在无构建工具的老服务器上运行,方案 B 的 REPL 模式(5.6 节)是 0.9 生态里效率最高的调试手段。两者可共存:方案 A 开发、方案 B 分发。
七、配置说明
Commons SCXML 是库而非服务,没有独立配置文件,全部「配置」体现在两处。
一是 JVM 选型。多 JDK 机器上用 update-alternatives 固定默认 java/javac(见 4.2 节),并给 Maven 显式 JAVA_HOME(实时回显):
echo "JAVA_HOME=$JAVA_HOME"
JAVA_HOME=
本机默认会话中 JAVA_HOME 为空,apt 版 Maven 通过 PATH 中的 java 定位 JDK 仍可工作;但只要机器上并存多个 JDK,建议显式导出(实时回显):
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 && echo "JAVA_HOME=$JAVA_HOME"
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
二是引擎行为参数。SCXMLExecutor 的三个构造参数(表达式引擎 Evaluator、事件派发器 EventDispatcher、错误上报 ErrorReporter)就是 0.9 的全部运行期配置面:默认示例传入 JexlEvaluator 与 SimpleErrorReporter,需要延时 <send> 时还要装配 SimpleScheduler(本专栏第 3、4 篇有完整示例)。运行日志走 commons-logging,默认绑定 JDK 自带的 JUL 输出,可通过常规 JCL 配置改接 log4j 等。
八、常见问题(FAQ)
Q1:运行报 NoClassDefFoundError: org/apache/xml/utils/PrefixResolver 怎么办?
这是最典型的一个坑:模型解析器引用了 Xalan 内部类,而 0.9 的 POM 把 xalan 声明为 optional 依赖。实测报错形如:
[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.6.4:java (default-cli) on project scxml-demo: An exception occurred while executing the Java class. org/apache/xml/utils/PrefixResolver: org.apache.xml.utils.PrefixResolver -> [Help 1]
解法:在 pom 中显式加入 xalan:xalan:2.7.2(见 4.3 节)。jar 直跑场景则把 xalan-2.7.2.jar 放进 classpath(见 5.4 节)。
Q2:JDK 17 上能用吗?
实测可以。本文作者在 Temurin 17.0.20.1 上以同一套工程完成了 4.6 节的全部验证(解析、事件流转、上下文读写、监听器、延时事件均正常),要点有二:其一,表达式引擎必须走 JEXL 路线(JexlEvaluator),0.9 备选的 JSP EL 路线需要额外装配 JSP/Servlet API,不适合现代 JVM 环境;其二,Java 侧根上下文要用配套的 JexlContext 而不是 SimpleContext,否则运行时报 SimpleContext cannot be cast to class org.apache.commons.scxml.env.jexl.JexlContext(完整报错见第 3 篇 3.2 节)。生产仍建议优先 JDK 8 同代环境,把兼容性风险归零。
Q3:控制台中文输出全是问号?
POSIX/C locale 下 JVM 默认用 ASCII 输出。两种解法:运行时加 -Dfile.encoding=UTF-8(本文所有 java 命令均已携带),或调整系统 locale(export LANG=C.UTF-8)。注意通过 mvn exec:java 运行时该参数要经 MAVEN_OPTS 传入,直接用 java -cp 更可控。
Q4:运行报 Could not find or load main class TrafficLightDemo?
类在 demo 包内,必须写全限定类名 demo.TrafficLightDemo(见 5.5 节);同时确认 -cp 同时包含 -d 输出目录与全部六个 jar,缺任一 jar 会转为 NoClassDefFoundError。
Q5:mvn -version 显示的 Java 版本不是我装的那个?
Ubuntu 上安装 maven 会连带装入 OpenJDK 11 作为默认 JRE。按 4.2 节用 update-alternatives --set java/javac 切回 8,并显式导出 JAVA_HOME 后再构建。
Q6:依赖树里为什么有 junit:junit:jar:3.8.1:compile?
commons-jexl:1.1 的老 POM 把 junit 写成了 compile 依赖(当年 POM 规范不严格),会随树进入你的测试与运行 classpath。生产工程建议排除:
<dependency>
<groupId>commons-jexl</groupId>
<artifactId>commons-jexl</artifactId>
<version>1.1</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
九、卸载与清理
Commons SCXML 本身不产生系统级安装,卸载即移除依赖与本地文件。
移除工程与实验目录(实时回显):
rm -rf /opt/scxml-demo/scxml-demo /opt/scxml-demo/jar-run /opt/scxml-demo/commons-scxml-0.9 /opt/scxml-demo/commons-scxml-0.9-bin.tar.gz /opt/scxml-demo/commons-scxml-0.9-bin.tar.gz.sha256
ls /opt/scxml-demo 2>&1
ls: cannot access '/opt/scxml-demo': No such file or directory
移除 Maven 本地仓库中的相关缓存(影响所有工程,慎用):
rm -rf $HOME/.m2/repository/commons-scxml $HOME/.m2/repository/commons-jexl $HOME/.m2/repository/xalan
卸载 JDK 与 Maven(仅当不再需要)。先以 -s 预演确认将删除的包,无误后去掉 -s 真正执行:
apt-get -s purge -y openjdk-8-jdk maven
# 输出节选(实测首尾)
The following packages will be REMOVED:
maven* openjdk-8-jdk*
0 upgraded, 0 newly installed, 2 to remove and 2 not upgraded.
Purg maven [3.6.3-5]
Purg openjdk-8-jdk [8u504-ga-1ubuntu1~22.04.3]
确认无误后真正卸载:
apt-get purge -y openjdk-8-jdk maven && apt-get autoremove -y
十、参考链接
- Apache Commons SCXML 官方主页:https://commons.apache.org/proper/commons-scxml/
- Apache Commons SCXML 官方下载目录:https://downloads.apache.org/commons/scxml/
- Commons SCXML 0.9 发布说明:https://downloads.apache.org/commons/scxml/RELEASE-NOTES.txt
- Maven 中央仓库坐标页:https://repo1.maven.org/maven2/commons-scxml/commons-scxml/
- W3C State Chart XML (SCXML) 规范:https://www.w3.org/TR/scxml/
- Apache Commons Dormant 项目名单:https://commons.apache.org/dormant.html