Apache Jelly 介绍与选型指南
面向 Ubuntu 22.04 + OpenJDK 17 实测环境,介绍 Apache Commons Jelly(XML 脚本引擎)的定位、核心架构、功能特性、停滞现状与同类选型对比,给出遗留系统的维护建议。
Apache Jelly 介绍与选型指南
目标:说清 Apache Commons Jelly 这个「把 XML 变成可执行代码」的 Java/XML 脚本引擎是什么、能干什么、与 Ant/Maven 旧生态的关系,以及在新项目中是否还值得选型。本文结论与版本信息均在 Ubuntu 22.04 + OpenJDK 17 环境下实测核实(commons-jelly 1.0.1 可在 JDK 17 上正常运行核心脚本)。
一、环境信息
本篇为选型调研篇,所有版本与结论均先在一台 Ubuntu 22.04 服务器上实测核实过,具体环境如下:
- 操作系统:Ubuntu 22.04(x86_64)
- JDK:OpenJDK 17.0.20.1(
apt仓库openjdk-17-jdk-headless) - 构建工具:Apache Ant 1.10.12(
apt仓库ant) - 实测对象:commons-jelly 1.0.1(Maven Central 最新正式版)
- 实测目录:
/opt/jelly/(依赖库)、/root/jelly-demo/(示例脚本)
版本核实命令与真实回显:
java -version
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
ant -version
Apache Ant(TM) version 1.10.12 compiled on January 17 1970
用 jelly 运行一个最小脚本确认「确实能跑」(脚本写法与集成步骤详见《Apache Jelly 在 Ubuntu 22.04 集成使用指南》):
jelly hello.jelly
Hello, Jelly!
Hello, Ubuntu 22.04!
💡 技术拓展:Java 环境版本选择上,老牌 Java 组件常面临「新 JDK 兼容性」问题,常见组合如下——
组合 原理/特点 适用场景 OpenJDK 8 老组件原生的目标运行时,兼容性最好 只求跑通、不愿排障 OpenJDK 11/17(LTS) 模块化后反射受限,但绝大多数普通反射仍可用 生产长期维护的主流选择 OpenJDK 21+ 最新 LTS,对更老的字节码仍向后兼容 新项目 本次实测 Jelly 1.0.1 在 OpenJDK 17 上核心功能可正常运行(含 CLI、核心标签、Java API 三种方式),故本文按 JDK 17 实测书写;仅
jelly -v因打包缺资源崩溃(见五、2)。
二、软件概述
2.1 Jelly 是什么
Apache Commons Jelly 是 Apache Commons 旗下的一个 Java 和 XML 基础上的脚本与处理引擎,口号是「Executable XML」——把 XML 文档变成可执行代码。它的官方自我定位是:
- 一种比 Ant 更灵活、更强大的「Ant 前端」(Maven 项目即为其代表);
- 一种测试框架(JellyUnit);
- 工作流/集成系统中的脚本引擎(如 werkflow);
- Cocoon 这类引擎里的页面模板系统。
Jelly 借鉴了 JSP 自定义标签、Velocity、Cocoon、Ant 的许多设计思想。一句话概括其形态:写一个 XML 文档,元素即「标签」,标签绑定到 Java 类(Tag),属性即标签属性,元素体即标签体;运行时引擎按文档顺序逐个执行标签,文本中的 ${...} 表达式由表达式引擎(默认 JEXL)求值后输出。
2.2 坐标与版本(实测确认)
Maven Central 上 Jelly 的真实坐标是 commons-jelly:commons-jelly(不是 org.apache.commons 前缀),这是排查依赖时最常踩的第一个坑。实测查询 Maven Central:
curl -s 'https://search.maven.org/solrsearch/select?q=g:commons-jelly&rows=5&wt=json' \
| python3 -c "import json,sys; [print(d['g'],d['a'],d['latestVersion'],d['p']) for d in json.load(sys.stdin)['response']['docs']]"
真实回显(该分组下共 numFound: 37 个构件,前 5 条):
commons-jelly commons-jelly 20040902.070806 jar
commons-jelly commons-jelly-tags-jaxme 20040902.072852 jar
commons-jelly commons-jelly-tags-threads 20040902.073749 jar
commons-jelly commons-jelly-tags-validate 20040902.073836 jar
commons-jelly commons-jelly-tags-email 20040902.072224 jar
仓库 maven-metadata.xml 明确 latest/release 均为 1.0.1(1.0.1 目录下仅有 jar/pom,无独立二进制发行包):
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>commons-jelly</groupId>
<artifactId>commons-jelly</artifactId>
<versioning>
<latest>1.0.1</latest>
<release>1.0.1</release>
<versions>
<version>1.0.1</version>
</versions>
<lastUpdated>20170924010015</lastUpdated>
</versioning>
</metadata>
历史版本结构(从 Central 目录列表节选):正式版本只有 1.0-RC1/1.0-RC2/1.0-beta-1 ~ beta-4/1.0/1.0.1,另有大量 20030211、20040902 形式的时间戳快照(2004-09-02 之后的最后一批快照),此后再无新版本。
2.3 停滞状态(重要,须如实披露)
选型前必须知道:Jelly 已长期停滞。以下每一项都是本次实测/核实过的证据:
| 证据项 | 实测结果 |
|---|---|
| 官网最后发布时间 | 2017-09-25(页面标注 "Last published: 25 September 2017",且站内文档为 "Doc for 1.1-SNAPSHOT",1.1 从未正式发布) |
| 官网维护状态声明 | 官网首页明确写着 "Warning: low development activity — Please note that Commons Jelly is enduring a phase with low activity of its developers"(低开发活跃度警告) |
| 源码最后提交 | GitHub 镜像 apache/commons-jelly(trunk 分支)最后提交时间 2017-09-27,此后无任何提交 |
| 最后正式版本 | 1.0.1;Central 上 1.0 的 jar 存档时间为 2005-11-22,1.0.1 的 jar 存档时间为 2017-09-20(系 Commons 批量补传 Central 所致,代码本身仍是 2005 年时代的) |
| Commons 组件页分类 | 仍列于 Released Components(未进入 Dormant 名单,但实际已无活动) |
| 打包缺陷 | Central 的 1.0.1 jar 缺少 jelly-version.txt 资源,导致命令行 jelly -v 直接抛 IllegalStateException(完整实测回显见《Apache Jelly 常用 CLI 命令与工具详解》四、3) |
⚠️ 注意:Jelly 不适合作为新项目的核心依赖引入。它的现实角色是「历史遗产」——2002~2006 年间大量 Apache 项目(最典型是 Maven 1.x)与老牌企业内部脚本以 Jelly 编写,运维这些系统时仍需理解它。
三、核心架构
3.1 运行时组件
Jelly 的运行时由五类组件构成,实测通过解包 jar 与源码核实:
jar tf /opt/jelly/lib/commons-jelly-1.0.1.jar | grep -E "org/apache/commons/jelly/[A-Za-z]+\.class"
org/apache/commons/jelly/CompilableTag.class
org/apache/commons/jelly/DynaBeanTagSupport.class
org/apache/commons/jelly/DynaTag.class
org/apache/commons/jelly/DynaTagSupport.class
org/apache/commons/jelly/Jelly.class
org/apache/commons/jelly/JellyContext.class
org/apache/commons/jelly/JellyException.class
org/apache/commons/jelly/JellyTagException.class
org/apache/commons/jelly/LocationAware.class
org/apache/commons/jelly/MapTagSupport.class
org/apache/commons/jelly/MissingAttributeException.class
org/apache/commons/jelly/NamespaceAwareTag.class
org/apache/commons/jelly/Script.class
org/apache/commons/jelly/Tag.class
org/apache/commons/jelly/TagLibrary.class
org/apache/commons/jelly/TagSupport.class
org/apache/commons/jelly/XMLOutput.class
各组件职责与关系:
| 组件 | 职责 | 关键点 |
|---|---|---|
XMLParser | SAX 解析 .jelly 文档,把元素编译为 Script 对象树 | 解析即编译,语法错误在此阶段暴露 |
Script | 可执行脚本单元(compile()/run(context, output)) | 编译产物可缓存复用(API 实测三、3 复用成功) |
JellyContext | 变量上下文(类似 Velocity 的 Context),负责变量读写与脚本查找 | Java 侧 setVariable/getVariable 与脚本侧 ${var} 双向互通(已实测) |
Tag/TagLibrary | 标签接口与标签库,XML 命名空间映射到标签库 | 映射表写在 jar 内 jelly.properties(见 3.2) |
XMLOutput | 输出通道(stdout/文件/SAX 流) | CLI -o 参数即切换输出通道(已实测) |
3.2 标签库机制
标签库通过命名空间 URI 绑定,默认注册表就在主 jar 的 org/apache/commons/jelly/jelly.properties 中(实测解包摘录):
# register the default tag libraries with an abbreviated name
core = org.apache.commons.jelly.tags.core.CoreTagLibrary
# optional taglibs
ant = org.apache.commons.jelly.tags.ant.AntTagLibrary
bean = org.apache.commons.jelly.tags.bean.BeanTagLibrary
define = org.apache.commons.jelly.tags.define.DefineTagLibrary
junit = org.apache.commons.jelly.tags.junit.JUnitTagLibrary
sql = org.apache.commons.jelly.tags.sql.SqlTagLibrary
util = org.apache.commons.jelly.tags.util.UtilTagLibrary
xml = org.apache.commons.jelly.tags.xml.XMLTagLibrary
swing/swt = ...(GUI 标签库)
(共 38 个映射,摘录)
⚠️ 注意:Central 上的主 jar 只内置
core标签库的实现类,ant/bean/define/util/xml/sql等均为独立构件(commons-jelly-tags-util、commons-jelly-tags-define之类),用时须单独下载并放进 classpath。本次实测jelly:ant标签库时,就因缺commons-jelly-tags-ant与 ant 相关 jar 而先后抛出NoClassDefFoundError(详见《Apache Jelly 常用 CLI 命令与工具详解》六、2)。
💡 技术拓展:XML 文档中的元素如何「绑定到代码」,业界有几种典型方案——
方案 原理 适用场景 Jelly Tag 元素绑定实现 Tag接口的 Java Bean,doTag()执行通用脚本/流程编排 JSP 自定义标签 JSP 运行时内执行的 Tag Handler Web 页面渲染 Ant Task build.xml元素绑定 Task 类构建脚本 XMLBeans 绑定 XML Schema 生成 Java 类做 数据绑定(非执行语义) 读写 XML 数据本身 Jelly 的 Tag 机制与 JSP 自定义标签、Ant Task 同源(属性注入 + 体执行),而 XMLBeans 只解决「XML ↔ Java 对象」映射、不含执行语义,二者不是同类工具。Jelly 实际采用自研 Tag 机制,可运行于 CLI、Ant、Maven、Servlet 等宿主。
3.3 表达式引擎
Jelly 默认使用 JEXL 1.0(commons-jexl-1.0.jar,POM 实测依赖)作为表达式语言,官方称其为「JSP/JSTL/JSF 表达式语言的超集」,并可通过 Jaxen 支持 XPath、通过 BSF 支持 JavaScript/BeanShell/Jython。
💡 技术拓展:Java 世界常见的表达式语言对比——
表达式语言 特点 典型宿主 JEXL 1.0 Velocity 风格, ${var.prop}/方法调用/算术比较Jelly(本次实测) JSTL EL 只读属性访问,无方法调用 JSP/JSTL OGNL 对象图导航能力强,支持链式索引 Struts2/旧 MyBatis XPath 按 XML 树路径取值 XSLT/Jelly(xml 标签库) 脚本语言(JS/BeanShell) 完整编程语言,最灵活 BSF 宿主 Jelly 默认采用 JEXL 1.0,但该版本语法能力有限——不支持列表字面量
[...]、不支持 Map 字面量、不支持对字符串字面量直接调方法(本次均实测复现报错,详见《Apache Jelly 在 Ubuntu 22.04 集成使用指南》七、常见问题)。
四、功能特性(实测)
4.1 命令行运行器
主 jar 自带 CLI 入口 org.apache.commons.jelly.Jelly,实测 jelly --help(参数逐项实测见《Apache Jelly 常用 CLI 命令与工具详解》):
usage: jelly [scriptFile] [-script scriptFile] [-o outputFile]
[-Dsysprop=syspropval] [-awt]
-swing Synonym of "-awt".
-awt Wether to run in the AWT thread.
-h,--help Give this help message
-o Output file
-script Jelly script to run
-v,--version prints Jelly's version and exits
4.2 核心标签库(32 个,源码实测)
从 1.0.1 sources jar 的 CoreTagLibrary.java 中提取的完整注册清单(实测命令与逐行回显):
curl -fsSL -o /opt/jelly/lib/commons-jelly-1.0.1-sources.jar https://repo1.maven.org/maven2/commons-jelly/commons-jelly/1.0.1/commons-jelly-1.0.1-sources.jar
unzip -p /opt/jelly/lib/commons-jelly-1.0.1-sources.jar org/apache/commons/jelly/tags/core/CoreTagLibrary.java | grep -E "registerTag"
registerTag("jelly", JellyTag.class);
registerTag("out", ExprTag.class);
registerTag("catch", CatchTag.class);
registerTag("forEach", ForEachTag.class);
registerTag("set", SetTag.class);
registerTag("remove", RemoveTag.class);
registerTag("while", WhileTag.class);
registerTag("if", IfTag.class);
registerTag("choose", ChooseTag.class);
registerTag("when", WhenTag.class);
registerTag("otherwise", OtherwiseTag.class);
registerTag("switch", SwitchTag.class);
registerTag("case", CaseTag.class);
registerTag("default", DefaultTag.class);
registerTag("include", IncludeTag.class);
registerTag("import", ImportTag.class);
registerTag("mute", MuteTag.class);
registerTag("arg", ArgTag.class);
registerTag("break", BreakTag.class);
registerTag("expr", ExprTag.class);
registerTag("file", FileTag.class);
registerTag("getStatic", GetStaticTag.class);
registerTag("invoke", InvokeTag.class);
registerTag("invokeStatic", InvokeStaticTag.class);
registerTag("new", NewTag.class);
registerTag("parse", ParseTag.class);
registerTag("scope", ScopeTag.class);
registerTag("setProperties", SetPropertiesTag.class);
registerTag("thread", ThreadTag.class);
registerTag("useBean", UseBeanTag.class);
registerTag("useList", UseListTag.class);
registerTag("whitespace", WhitespaceTag.class);
支持条件(if/choose/switch)、循环(forEach/while)、异常捕获(catch)、Java 互操作(new/invoke/invokeStatic/getStatic/useBean/useList)等完整脚本能力。以下为核心标签示例的真实运行回显(脚本全文见《Apache Jelly 在 Ubuntu 22.04 集成使用指南》六、2):
1) set+expr: Ubuntu 22.04 + 1.0.1
list.size = 3
2) forEach item = nginx / redis / jelly
3) forEach index i = 1 3 5
4) if: os is Ubuntu 22.04
5) choose: ver < 2.0
6) while n = 0 1 2
7) switch: Ubuntu 22.04 branch
JELLY (8) expr tag printed above
9) invokeStatic System.currentTimeMillis = true
9) getStatic File.pathSeparator = [:]
10) catch captured: org.apache.commons.jelly.JellyTagException: ... could not include ...
4.3 与 Ant/Maven 旧生态的关系
- Jelly 曾是 Maven 1 的内核:Maven 1.x(2002-2007 主流)的构建目标(goal)即以 Jelly 脚本编写,
maven命令本质上是运行 Jelly 引擎;官方 Jelly 首页也把 Maven 列为「Jelly 作为更灵活的 Ant 前端」的头号示例。这正是 Jelly 在 Java 生态的历史地位来源,也是它至今仍被老项目引用的根本原因。 - Jelly 与 Ant 双向集成(本次均实测跑通):
- Ant → Jelly:Ant 提供
org.apache.commons.jelly.task.JellyTask(在commons-jelly-tags-ant构件中),build.xml里<taskdef>后即可把 Jelly 脚本作为构建步骤,Ant 的Project对象会自动注入脚本上下文为project变量(实测回显project.name = jelly-demo); - Jelly → Ant:
jelly:ant标签库可在 Jelly 脚本中直接使用全部 Ant 任务,实测在脚本里执行<ant:mkdir>、<ant:echo>均成功。
- Ant → Jelly:Ant 提供
4.4 Java API 嵌入
通过 JellyContext 可把 Jelly 嵌入任意 Java 程序,实测三种调用方式全部通过(完整代码与输出见《Apache Jelly 在 Ubuntu 22.04 集成使用指南》六、3):
== mode 1: runScript ==
Hello server-admin from JellyContext API! (x3)
variable written by script: greeting = done-3
== mode 2: compileScript + run ==
Hello root from JellyContext API! (x7)
variable written by script: greeting = done-7
== mode 3: reuse compiled Script ==
Hello root from JellyContext API! (x9)
variable written by script: greeting = done-9
五、适用场景与限制
5.1 适用场景
- 遗留系统维护:Maven 1 项目、老 ETL/发布脚本、JellyUnit 测试用例的解读与小修;
- 存量脚本运维:仍以 XML 表达构建/编排逻辑、且不愿迁移的老环境;
- 教学/考古:理解 JSP 标签、Ant Task、模板引擎同源的设计思想。
5.2 限制(实测结论)
| 限制项 | 实测事实 |
|---|---|
| 无新版本 | 最后一版 1.0.1(2005 年代码),最后提交 2017-09-27 |
| 无官方独立发行包 | Central 只有 jar/pom/javadoc,无带启动脚本的 tar 包,需自行组装 classpath |
| 表达式语法受限 | JEXL 1.0 不支持列表/Map 字面量、不支持字符串字面量调方法(实测复现) |
jelly -v 不可用 | Central jar 缺 jelly-version.txt,抛 IllegalStateException(实测) |
| 脚本错误退出码为 0 | Jelly.main 捕获 JellyException 后仅打印堆栈正常返回(源码核实),自动化脚本不能依赖退出码判失败 |
| 文档陈旧 | 官网文档停留在 1.1-SNAPSHOT(2017-09-25),部分链接已失效 |
5.3 维护建议
- 新项目不要选 Jelly;已运行的 Jelly 脚本建议按优先级迁移:纯模板 → Velocity/FreeMarker;构建逻辑 → Ant/Maven/Gradle;编排逻辑 → 现代工作流引擎或直接 Java/Python。
- 必须保留的存量 Jelly 环境:固定 JDK 版本(实测 JDK 17 可用)、锁定依赖 jar 清单(见《Apache Jelly 在 Ubuntu 22.04 集成使用指南》二、2 的依赖集合表)、把
-v崩溃与退出码问题写进运维预案。
六、同类选型对比
6.1 横向对比表
| 维度 | Apache Jelly | Velocity | FreeMarker | JSP 自定义标签 | Ant build.xml | XSLT |
|---|---|---|---|---|---|---|
| 形态 | XML 脚本引擎 | 文本模板引擎 | 文本模板引擎 | Web 模板标签 | 构建脚本 | XML 变换语言 |
| 语法载体 | XML 元素=标签 | ${...}+指令 | ${...}+指令 | XML 标签 | XML 元素=Task | XML+XPath |
| 表达式 | JEXL 1.0(受限) | VTL | FTL(强) | EL | 属性/依赖 | XPath |
| 逻辑能力 | 完整(循环/分支/异常/Java 互操作) | 宏+受限逻辑 | 完整宏体系 | 标签内 | Task 链 | 内置函数 |
| 现状(2026 实测核实) | 停滞(最后提交 2017-09-27) | 活跃(2.x) | 活跃(2.3.x) | 仍随 Jakarta EE | 活跃(1.10.x) | 内置于各语言 |
| 适合 | 存量维护 | 代码生成/邮件模板 | 模板渲染 | Web 视图 | 构建 | XML 转换 |
关键差异:Jelly 是唯一「把 XML 当完整脚本语言」的方案(可定义新标签、可嵌 Java 互操作),其余方案要么是纯文本模板(Velocity/FreeMarker),要么只覆盖构建(Ant)或变换(XSLT)。能选 Velocity/FreeMarker 就不要选 Jelly——除非面对的是必须原样运行的存量 Jelly 脚本。
6.2 选型决策树
需求是「让 XML 变成可执行逻辑」吗?
├─ 否(只是模板渲染/代码生成)→ Velocity / FreeMarker
├─ 是,且是构建场景 → Ant / Maven / Gradle
├─ 是,且是存量 Jelly 脚本 → 维持 Jelly(本文环境实测可跑)
└─ 是,新项目 → 不要引入 Jelly;改用原生 Java/Groovy 脚本或现代编排引擎
七、参考链接
- 官网(含 low activity 声明):https://commons.apache.org/proper/commons-jelly/
- Maven Central 坐标:https://central.apache.org/ →
commons-jelly:commons-jelly - 全版本目录:https://repo1.maven.org/maven2/commons-jelly/commons-jelly/
- 源码镜像:https://github.com/apache/commons-jelly
- 集成实操:《Apache Jelly 在 Ubuntu 22.04 集成使用指南》
- 命令行详解:《Apache Jelly 常用 CLI 命令与工具详解》