Apache Anakia 快速入门与集成示例
Apache Anakia 快速入门实战:从零创建 XML 源文档与 site.vsl 模板,build.xml 注册 AnakiaTask 跑通 XML 转 HTML 并验证产物,含一键脚本与 systemd 定时构建集成,全程实测。
Apache Anakia 快速入门与集成示例
目标:从零搭建一个可运行的 Anakia 文档站点工程——创建 XML 源文档与 navigation 导航、编写 Velocity 模板(site.vsl)与引擎属性(velocity.properties)、在 build.xml 中注册并配置 AnakiaTask,完成首次 XML→HTML 转换并逐项验证产物(UTF-8 中文、导航菜单、实体转义、构建时间戳),最后给出「一键构建脚本 + systemd 定时构建」两个集成示例,全部步骤在 Ubuntu 22.04(OpenJDK 1.8.0_504、Ant 1.10.18、Velocity 1.7)实测通过。
一、环境信息
本篇所有命令与回显均在以下环境中实测采集:
- 操作系统:Ubuntu 22.04.5 LTS,x86_64
- JDK:OpenJDK 1.8.0_504(
/usr/lib/jvm/java-8-openjdk-amd64) - 构建工具:Apache Ant 1.10.18(
ANT_HOME=/opt/apache-ant-1.10.18) - 组件版本:Apache Anakia(
org.apache.velocity:velocity:1.7及其依赖四 jar 位于/opt/anakia-lib/) - 演示工程:
/opt/anakia-quick(本篇从零创建) - 执行用户:root;演示账号 admin / q1w2e3r4(本篇为本地构建操作,如涉及远程登录使用该账号)
💡 技术拓展:Anakia 的名字取自「Apache + ia」且
style属性名带有历史色彩——它对标的是 XSLT 转换,但实际使用的不是 XSL 样式表,而是 Velocity 模板(.vsl):XML 由 JDOM 解析成元素树交给 Velocity 上下文($root),输出格式完全由 VTL 控制。因此「XML+XSL 转 HTML」在 Anakia 里应理解为「XML + Velocity 样式模板转 HTML」,不需要任何 XSLT 处理器。
二、前置条件
Anakia 环境就位(JDK + Ant + 四个运行时 jar)是本篇前提,安装过程见《Ubuntu 22.04 安装 Apache Anakia 指南》。核验命令与实测回显:
which ant && ant -version
回显(实测):
/opt/apache-ant-1.10.18/bin/ant
Apache Ant(TM) version 1.10.18 compiled on September 3 2026
ls /opt/anakia-lib/
回显(实测):
antlr-2.7.2.jar
commons-collections-3.2.2.jar
commons-lang-2.6.jar
jdom-1.1.3.jar
saxpath-1.0-FCS.jar
velocity-1.7.jar
werken-xpath-0.9.4.jar
(velocity-1.7.jar + commons-lang + commons-collections + jdom 四个是 Anakia 运行必需;antlr/saxpath/werken-xpath 三个仅在模板使用 $xpath 时需要,见《配置参数详解》第六章。)
三、Anakia 工作原理与工程结构
Anakia 的一次构建分三步:① JDOM SAXBuilder 解析 basedir 下每个 .xml 源文件;② 把根元素、导航文档、工具对象注入 Velocity 上下文,用 style 指定的 .vsl 模板渲染;③ 输出按 extension 命名写入 destdir。它无常驻进程、无端口,每次 ant 都是一个全新 JVM 完成全部转换后退出。
本篇演示工程的目录结构(转换前):
anakia-quick/
├── build.xml # Ant 构建文件:注册 AnakiaTask 并配置参数
├── src/xml/
│ ├── index.xml # 首页源文档
│ ├── guide.xml # 构建指南页源文档
│ └── navigation.xml # 站点导航(注入 $project,不进产物)
└── styles/
├── site.vsl # Velocity 模板(站点外观)
└── velocity.properties # 引擎属性(输出/输入编码)
六个文件的内容与写法见第四~六章,全部给出可直接粘贴的完整版本;第七章执行首次构建,第八章做集成。
四、编写 XML 源文档与导航文件
创建目录骨架并写入两个源文档与导航文件。index.xml:
mkdir -p /opt/anakia-quick/src/xml /opt/anakia-quick/styles && cat > /opt/anakia-quick/src/xml/index.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<document>
<title>Anakia 快速入门站点</title>
<section name="简介">
<p>本站点由 Apache Anakia 从 XML 源文档自动生成,模板引擎为 Velocity 1.7。</p>
<p>XML 源文件放在 src/xml 目录,Velocity 模板放在 styles 目录。</p>
</section>
<section name="转义演示">
<p>源文档里写 & 与 <tag> 属于 XML 实体,输出阶段由模板的 escape 工具再次转义。</p>
</section>
</document>
EOF
guide.xml:
cat > /opt/anakia-quick/src/xml/guide.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<document>
<title>构建指南</title>
<section name="构建命令">
<p>进入工程目录执行 ant 即可完成 XML 到 HTML 的转换。</p>
</section>
<section name="增量构建">
<p>Anakia 默认启用 lastModifiedCheck,源文件与模板均未变化时跳过转换。</p>
</section>
</document>
EOF
navigation.xml(站点名与菜单,模板里通过 $project 读取):
cat > /opt/anakia-quick/src/xml/navigation.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project name="anakia-quick">
<title>Anakia Quick Site</title>
<menu>
<item name="首页" href="index.html"/>
<item name="构建指南" href="guide.html"/>
</menu>
</project>
EOF
三个要点:① heredoc 定界符写 <<'EOF'(带单引号)是为了让 $root、#foreach 等 VTL 记号原样落盘,不被 shell 展开——第五、六章同理;② 源文档约定根元素 document、子元素 title/section/p,这是模板侧的解析契约(换成自己的 DTD 时模板同步改);③ 正文中的 & 必须写成 &、< 写成 <——源文件首先是合法 XML(违反时的真实报错见 9.1 节)。
五、编写 Velocity 模板与引擎属性
styles/site.vsl——站点唯一外观定义,覆盖导航渲染、正文遍历、实体转义、构建时间戳四个能力:
cat > /opt/anakia-quick/styles/site.vsl <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<title>$root.getChild("title").getTextTrim()</title>
</head>
<body>
<div id="nav">
#foreach ($item in $project.getChild("menu").getChildren("item"))
<a href="$item.getAttributeValue('href')">$item.getAttributeValue("name")</a>
#end
</div>
<h1>$root.getChild("title").getTextTrim()</h1>
#foreach ($node in $root.getChildren("section"))
<h2>$node.getAttributeValue("name")</h2>
#foreach ($para in $node.getChildren("p"))
<p>$escape.getText($para.getTextTrim())</p>
#end
#end
<hr/>
<p>Generated by Apache Anakia / Velocity 1.7 at $date</p>
</body>
</html>
EOF
模板逐行说明($ 与 # 开头均为 VTL 指令/引用,属程序逻辑而非占位符):$root 是当前源文档的 JDOM 根元素,.getChild("title").getTextTrim() 取子元素文本;#foreach ... #end 遍历 getChildren() 返回的元素列表;$project 是 navigation.xml 的 JDOM 文档,菜单由它渲染;$escape 是 Anakia 内置 HTML 转义工具(Escape.getText(String)),防止正文中的 &、< 破坏 HTML 结构;$date 是转换时刻的 java.util.Date。JDOM 方法调用语法见 https://jdom.org/docs/apidocs/1.1.3/ 。
styles/velocity.properties——中文站点的关键两行(缺省时 Velocity 输出编码回退 ISO-8859-1,中文全部变 ?,实测过程见《配置参数详解》5.4 节):
cat > /opt/anakia-quick/styles/velocity.properties <<'EOF'
output.encoding=UTF-8
input.encoding=UTF-8
EOF
六、编写 build.xml:注册并配置 AnakiaTask
build.xml 完整内容(taskdef 把 AnakiaTask 注册为 anakia 标签,docs 目标执行转换,另配 clean 与 rebuild 两个辅助目标):
cat > /opt/anakia-quick/build.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project name="anakia-quick" default="docs" basedir=".">
<property name="docs.src" location="${basedir}/src/xml"/>
<property name="docs.dest" location="${basedir}/docs"/>
<taskdef name="anakia" classname="org.apache.velocity.anakia.AnakiaTask">
<classpath>
<pathelement location="/opt/anakia-lib/velocity-1.7.jar"/>
<pathelement location="/opt/anakia-lib/commons-lang-2.6.jar"/>
<pathelement location="/opt/anakia-lib/commons-collections-3.2.2.jar"/>
<pathelement location="/opt/anakia-lib/jdom-1.1.3.jar"/>
</classpath>
</taskdef>
<target name="docs" description="Transform XML docs to HTML with Anakia">
<mkdir dir="${docs.dest}"/>
<anakia basedir="${docs.src}" destdir="${docs.dest}"
extension=".html"
style="site.vsl"
templatePath="${basedir}/styles"
velocityPropertiesFile="${basedir}/styles/velocity.properties"
projectFile="navigation.xml"
lastModifiedCheck="true"
excludes="navigation.xml"/>
</target>
<target name="clean" description="Remove generated HTML">
<delete dir="${docs.dest}"/>
</target>
<target name="rebuild" depends="clean,docs" description="Clean then full rebuild"/>
</project>
EOF
逐项说明(<anakia> 十个属性完整语义见《配置参数详解》第四、五章;property 元素里的 ${basedir} 等为 Ant 属性引用,运行时求值,属构建脚本程序逻辑而非占位符):
taskdef的<classpath>内嵌四个运行时 jar 的绝对路径——换机器部署只需改这一段;basedir/destdir指向src/xml与docs(目录自动创建);style="site.vsl"相对templatePath解析,模板放styles/下;velocityPropertiesFile显式指向styles/velocity.properties,不依赖「当前目录碰巧有同名文件」;projectFile="navigation.xml"相对basedir(即src/xml/)解析;excludes="navigation.xml"防止导航文件本身被转成navigation.html;lastModifiedCheck="true"保持增量(该属性本就是默认值,写出来是为了自文档化)。
七、首次构建与产物验证
执行首次构建:
cd /opt/anakia-quick && ant
回显(实测):
Buildfile: /opt/anakia-quick/build.xml
docs:
[mkdir] Created dir: /opt/anakia-quick/docs
[anakia] Transforming into: /opt/anakia-quick/docs
[anakia] Input: index.xml
[anakia] Output: /opt/anakia-quick/docs/index.html
[anakia] Input: guide.xml
[anakia] Output: /opt/anakia-quick/docs/guide.html
BUILD SUCCESSFUL
Total time: 0 seconds
(navigation.xml 被 excludes 挡住,不出现 Input/Output 行。)验证首页产物完整内容:
cat /opt/anakia-quick/docs/index.html
回显(实测,全文):
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<title>Anakia 快速入门站点</title>
</head>
<body>
<div id="nav">
<a href="index.html">首页</a>
<a href="guide.html">构建指南</a>
</div>
<h1>Anakia 快速入门站点</h1>
<h2>简介</h2>
<p>本站点由 Apache Anakia 从 XML 源文档自动生成,模板引擎为 Velocity 1.7。</p>
<p>XML 源文件放在 src/xml 目录,Velocity 模板放在 styles 目录。</p>
<h2>转义演示</h2>
<p>源文档里写 & 与 <tag> 属于 XML 实体,输出阶段由模板的 escape 工具再次转义。</p>
<hr/>
<p>Generated by Apache Anakia / Velocity 1.7 at Tue Oct 13 15:37:26 GMT 2026</p>
</body>
</html>
对照源文档逐项核对:中文全部完整(UTF-8 配置生效);导航两项来自 navigation.xml($project 生效);正文 &/<tag> 输出为 HTML 实体形式($escape 生效,浏览器中显示为 & 与 <tag>);末行 at Tue Oct 13 15:37:26 GMT 2026 为构建时刻($date 生效)。
增量行为验证——只修改 guide.xml 再跑一次,应只重转该文件:
cd /opt/anakia-quick && touch src/xml/guide.xml && ant
回显(实测):
Buildfile: /opt/anakia-quick/build.xml
docs:
[anakia] Transforming into: /opt/anakia-quick/docs
[anakia] Input: guide.xml
[anakia] Output: /opt/anakia-quick/docs/guide.html
BUILD SUCCESSFUL
Total time: 0 seconds
全量重建用 rebuild 目标(先 clean 删产物目录、再 docs 全转,依赖链清晰可见):
cd /opt/anakia-quick && ant rebuild
回显(实测):
Buildfile: /opt/anakia-quick/build.xml
clean:
[delete] Deleting directory /opt/anakia-quick/docs
docs:
[mkdir] Created dir: /opt/anakia-quick/docs
[anakia] Transforming into: /opt/anakia-quick/docs
[anakia] Input: guide.xml
[anakia] Output: /opt/anakia-quick/docs/guide.html
[anakia] Input: index.xml
[anakia] Output: /opt/anakia-quick/docs/index.html
rebuild:
BUILD SUCCESSFUL
Total time: 0 seconds
ant -p 确认三个目标及默认目标:
cd /opt/anakia-quick && ant -p
回显(实测):
Buildfile: /opt/anakia-quick/build.xml
Main targets:
clean Remove generated HTML
docs Transform XML docs to HTML with Anakia
rebuild Clean then full rebuild
Default target: docs
八、集成示例:一键构建脚本与 systemd 定时构建
8.1 一键构建脚本
把「全量重建」固化为脚本,供手动发布或 CI 调用:
printf "#!/bin/bash\nset -e\ncd /opt/anakia-quick\n/opt/apache-ant-1.10.18/bin/ant rebuild\n" > /opt/anakia-quick/build-site.sh && chmod +x /opt/anakia-quick/build-site.sh
(脚本用绝对路径调用 ant,避免受执行者 PATH 差异影响;set -e 保证 ant 失败时脚本以非零码退出。)实测执行:
/opt/anakia-quick/build-site.sh
回显(实测):
Buildfile: /opt/anakia-quick/build.xml
clean:
[delete] Deleting directory /opt/anakia-quick/docs
docs:
[mkdir] Created dir: /opt/anakia-quick/docs
[anakia] Transforming into: /opt/anakia-quick/docs
[anakia] Input: guide.xml
[anakia] Output: /opt/anakia-quick/docs/guide.html
[anakia] Input: index.xml
[anakia] Output: /opt/anakia-quick/docs/index.html
rebuild:
BUILD SUCCESSFUL
Total time: 0 seconds
8.2 systemd 定时构建
用 systemd timer 实现每夜 02:30 自动重建。先写 service 单元(oneshot 型,执行一次即退出):
printf "[Unit]\nDescription=Anakia nightly site build\n\n[Service]\nType=oneshot\nUser=root\nWorkingDirectory=/opt/anakia-quick\nExecStart=/opt/anakia-quick/build-site.sh\n" > /etc/systemd/system/anakia-build.service
再写 timer 单元(OnCalendar 每日 02:30;Persistent=true 表示停机错过的触发点会在开机后补跑一次):
printf "[Unit]\nDescription=Run Anakia site build nightly\n\n[Timer]\nOnCalendar=*-*-* 02:30:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n" > /etc/systemd/system/anakia-build.timer
加载并启用(每条 systemctl 命令只操作一个单元):
systemctl daemon-reload
systemctl enable --now anakia-build.timer
回显(实测):
Created symlink /etc/systemd/system/timers.target.wants/anakia-build.timer → /etc/systemd/system/anakia-build.timer.
查看下次触发时间:
systemctl list-timers anakia-build.timer --no-pager
回显(实测):
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2026-10-14 02:30:00 UTC 10h left n/a n/a anakia-build.timer anakia-build.service
1 timers listed.
Pass --all to see loaded but inactive timers, too.
不等到夜里,手动触发一次验证全链路(service 由 timer 拉起后执行脚本、脚本执行 ant):
systemctl start anakia-build.service && systemctl status anakia-build.service --no-pager | head -6
回显(实测):
○ anakia-build.service - Anakia nightly site build
Loaded: loaded (/etc/systemd/system/anakia-build.service; static)
Active: inactive (dead) since Tue 2026-10-13 15:38:06 UTC; 10ms ago
TriggeredBy: ● anakia-build.timer
Process: 15240 ExecStart=/opt/anakia-quick/build-site.sh (code=exited, status=0/SUCCESS)
Main PID: 15240 (code=exited, status=0/SUCCESS)
(oneshot 服务执行成功后处于 inactive (dead) 是正常状态,成败看 status=0/SUCCESS。)构建日志进入 journal:
journalctl -u anakia-build.service --no-pager | grep -E "anakia|BUILD" | head -6
回显(实测):
Oct 13 15:38:05 anakia-u22 systemd[1]: Starting Anakia nightly site build...
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]: [anakia] Transforming into: /opt/anakia-quick/docs
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]: [anakia] Input: guide.xml
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]: [anakia] Output: /opt/anakia-quick/docs/guide.html
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]: [anakia] Input: index.xml
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]: [anakia] Output: /opt/anakia-quick/docs/index.html
定时构建的产物时间戳在每夜刷新,若前端有静态文件缓存,可在脚本里追加版本号改名或加 ETag 策略——超出本篇范围,此处给出的是「源变更 → 定时重建 → 产物目录」的最小闭环。timer 单元确认处于 active (waiting):
systemctl status anakia-build.timer --no-pager | head -4
回显(实测):
● anakia-build.timer - Run Anakia site build nightly
Loaded: loaded (/etc/systemd/system/anakia-build.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2026-10-13 15:38:00 UTC; 6s ago
Trigger: Wed 2026-10-14 02:30:00 UTC; 10h left
九、常见问题(FAQ)
Q1:源 XML 里直接写 & 或 < 会怎样?
源文件解析失败。实测把 <p>A&B 与转义演示</p> 这样带裸 & 的段落原样写进 index.xml 第九行后构建(照实回显,注意最后仍是 BUILD SUCCESSFUL):
Buildfile: /opt/anakia-quick/build.xml
docs:
[mkdir] Created dir: /opt/anakia-quick/docs
[anakia] Transforming into: /opt/anakia-quick/docs
[anakia] Input: guide.xml
[anakia] Output: /opt/anakia-quick/docs/guide.html
[anakia] Input: index.xml
[anakia]
[anakia] Error: The reference to entity "B" must end with the ';' delimiter.
[anakia] Line: 9 Column: 11
[anakia]
BUILD SUCCESSFUL
Total time: 0 seconds
两个教训:① & 必须写成 &、< 必须写成 <(或放进 CDATA 段);② AnakiaTask 对单个文件的解析/渲染失败只记 Error: 后继续处理其余文件,构建整体仍成功、该文件没有 HTML 产出——CI 里建议对日志做 grep -c "\[anakia\] Error:" 断言或比对 Input/Output 行数(该行为与排查手段见《配置参数详解》FAQ Q1)。
Q2:改了模板,产物没变化?
先确认改的是 templatePath 指向目录里的模板文件(本工程 styles/site.vsl),且没有把文件写进别处副本;再跑 ant rebuild 强制全量。增量机制下模板时间戳参与判定(正常 ant 就会重转引用它的文件,实测见《配置参数详解》5.3 节),只有系统时钟异常时才需要 rebuild。
Q3:生成的 HTML 中文乱码/变问号?
缺 output.encoding=UTF-8。Anakia 写文件用 Velocity 的 output.encoding,缺省回退 ISO-8859-1,JVM 层面改 file.encoding 无效。把第五节的 velocity.properties 配好并通过 velocityPropertiesFile 指向它即可,实测对比过程见《配置参数详解》5.4 节。
Q4:想加一个新页面需要动哪些文件?
三步:src/xml/ 下新增 <页面名>.xml(沿用 document/section/p 结构);src/xml/navigation.xml 菜单里加一个 <item>;跑 ant。模板与 build.xml 都不用动——这正是「结构与外观分离」后日常运维的成本。
Q5:Anakia 适合替代 Hugo/Jekyll 这类静态站生成器吗?
定位不同。Anakia 胜在与 Java 技术栈零依赖集成(一个 ant 命令、无运行时进程)、模板即 Velocity(Java 团队熟悉)、行为完全确定(每次全新 JVM);但它没有 Markdown 支持、没有主题生态、没有热重载。文档型站点与 Java 项目文档本地化(如源码 XML 化后的站点化)适合 Anakia;内容创作型站点选主流静态站生成器更省力。
十、参考链接
- Anakia 官方文档(Velocity 引擎站点内):https://velocity.apache.org/engine/devel/anakia.html
- Velocity 1.7 用户手册(VTL 语法与指令):https://velocity.apache.org/engine/1.7/user-guide.html
- JDOM 1.1.3 API(模板中 $root/$project 的方法来源):https://jdom.org/docs/apidocs/1.1.3/
- Ant 构建文件手册(project/target/property):https://ant.apache.org/manual/using.html
- Ant taskdef 任务手册:https://ant.apache.org/manual/Tasks/taskdef.html
- systemd.timer 官方手册(OnCalendar/Persistent):https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html
- Maven 中央仓库 velocity 1.7:https://repo1.maven.org/maven2/org/apache/velocity/velocity/1.7/