Ubuntu 22.04 安装 Apache Commons SCXML 指南

在 Ubuntu 22.04 上从零安装 Apache Commons SCXML 0.9(2008 年最终版),覆盖 OpenJDK 8 与 Maven 环境、optional 依赖补齐、官方 bin 包与 jar 直跑两种方案、sha256 校验与官方交互式验证工具,命令回显均实测通过。

Apache Commons SCXMLApache Commons SCXML2026/10/15

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,联网即可获取。

  1. 具有 root 或 sudo 权限的 Ubuntu 22.04 系统。
  2. 可访问 Ubuntu 官方软件源(apt update 能正常拉取索引)。
  3. 可访问 Apache 官方下载站 downloads.apache.org 与 Maven 中央仓库 repo1.maven.org(本文实测网络直连可达)。
  4. 至少 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.82007 年及更早commons-scxml:commons-scxml:0.8仅维护遗留系统时接触
0.9(最终版)0.92008-11-22commons-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 SCXMLW3C SCXML 标准 XML 文件模型与代码彻底分离,模型可热更、可跨语言复用对模型标准化/可审计有要求的场景
Spring StatemachineJava DSL + 配置类与 Spring 生态深度整合,有持久化/监控扩展Spring 技术栈的新项目
Squirrel FoundationJava 注解/构建器轻量高性能,函数式风格嵌入式、性能敏感的纯 Java 场景
Stateless4jJava 流式 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 compileBUILD SUCCESS报缺类 → FAQ Q1;报找不到符号 → 检查源码目录 src/main/java/demo
依赖树 compile 节点6 个 jar(含 serializer、xml-apis 传递节点)缺 jexl/xalan → 4.3 节 pom 漏抄
运行首行模型解析成功, 根状态个数=3NoClassDefFoundError: 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

十、参考链接

  1. Apache Commons SCXML 官方主页:https://commons.apache.org/proper/commons-scxml/
  2. Apache Commons SCXML 官方下载目录:https://downloads.apache.org/commons/scxml/
  3. Commons SCXML 0.9 发布说明:https://downloads.apache.org/commons/scxml/RELEASE-NOTES.txt
  4. Maven 中央仓库坐标页:https://repo1.maven.org/maven2/commons-scxml/commons-scxml/
  5. W3C State Chart XML (SCXML) 规范:https://www.w3.org/TR/scxml/
  6. Apache Commons Dormant 项目名单:https://commons.apache.org/dormant.html