Apache Jelly 介绍与选型指南

面向 Ubuntu 22.04 + OpenJDK 17 实测环境,介绍 Apache Commons Jelly(XML 脚本引擎)的定位、核心架构、功能特性、停滞现状与同类选型对比,给出遗留系统的维护建议。

Apache JellyApache JellyJavaXMLApache2026/10/16

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

各组件职责与关系:

组件职责关键点
XMLParserSAX 解析 .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 HandlerWeb 页面渲染
Ant Taskbuild.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.0Velocity 风格,${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> 均成功。

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(实测)
脚本错误退出码为 0Jelly.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 JellyVelocityFreeMarkerJSP 自定义标签Ant build.xmlXSLT
形态XML 脚本引擎文本模板引擎文本模板引擎Web 模板标签构建脚本XML 变换语言
语法载体XML 元素=标签${...}+指令${...}+指令XML 标签XML 元素=TaskXML+XPath
表达式JEXL 1.0(受限)VTLFTL(强)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 脚本或现代编排引擎

七、参考链接