Apache Velocity 快速入门与集成示例
Apache Velocity 2.4.1 快速入门:从 archetype 生成 Maven 工程到变量、条件、循环、宏与 Toolbox 工具箱集成,逐步附编译运行真实回显与 JVM 堆内存验证方法,可照抄复现。
Apache Velocity 快速入门与集成示例
一、环境信息
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS(CentOS 8 实测行为一致) |
| 登录账号 | root |
| JDK | OpenJDK 17.0.20.1 |
| Maven | 3.6.3 |
| 组件 | velocity-engine-core 2.4.1 |
| 辅助依赖 | slf4j-simple 1.7.36、velocity-tools-generic 3.1 |
| 工程目录 | /root/velocity-quickstart、/root/velocity-demo |
二、前置条件
- 已安装 JDK 17 与 Maven 3.6.3,
mvn -v可正常回显。 - 服务器可访问 Maven Central。
- 会话 locale 为 UTF-8(最小环境需执行
export LANG=C.UTF-8,否则控制台中文乱码)。
三、工程创建
用 Maven 原型插件生成标准骨架:
cd /root && mvn archetype:generate -DgroupId=com.example -DartifactId=velocity-quickstart -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
实测回显(节选):
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 49.631 s
[INFO] Finished at: 2026-10-12T09:29:34Z
[INFO] ------------------------------------------------------------------------
删除原型自带的测试目录,创建资源目录:
rm -rf /root/velocity-quickstart/src/test
mkdir -p /root/velocity-quickstart/src/main/resources/templates
四、基础渲染示例
4.1 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>com.example</groupId>
<artifactId>velocity-quickstart</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
4.2 主类 App.java
src/main/java/com/example/App.java:
package com.example;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import java.io.StringWriter;
import java.util.Arrays;
public class App {
public static void main(String[] args) {
VelocityEngine engine = new VelocityEngine();
engine.setProperty("resource.loaders", "classpath");
engine.setProperty("resource.loader.classpath.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.setProperty("resource.default_encoding", "UTF-8");
engine.init();
VelocityContext context = new VelocityContext();
context.put("product", "quzuo.net");
context.put("modules", Arrays.asList("技术专栏", "文档发布", "归档中心"));
Template template = engine.getTemplate("templates/hello.vm", "UTF-8");
StringWriter writer = new StringWriter();
template.merge(context, writer);
System.out.println(writer);
}
}
4.3 模板 hello.vm
src/main/resources/templates/hello.vm:
欢迎来到 $product!
模块列表:
#foreach($m in $modules)
- $m
#end
共 $modules.size() 个模块。
4.4 编译与运行
cd /root/velocity-quickstart && mvn clean compile
实测回显(节选):
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.292 s
cd /root/velocity-quickstart && LANG=C.UTF-8 mvn -q exec:java -Dexec.mainClass=com.example.App
实测回显:
欢迎来到 quzuo.net!
模块列表:
- 技术专栏
- 文档发布
- 归档中心
共 3 个模块。
五、进阶模板特性集成
5.1 测试工程与数据模型
在 /root/velocity-demo 建第二个工程,pom 在 4.1 基础上追加 velocity-tools-generic 3.1 依赖:
<dependency>
<groupId>org.apache.velocity.tools</groupId>
<artifactId>velocity-tools-generic</artifactId>
<version>3.1</version>
</dependency>
主类 VelocityDemo 构建完整数据模型,并启用外部宏库:
package com.example;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class VelocityDemo {
private static Map<String, Object> item(String name, double price) {
Map<String, Object> item = new HashMap<>();
item.put("name", name);
item.put("price", price);
return item;
}
public static void main(String[] args) throws Exception {
System.out.println("JVM maxMemory = "
+ Runtime.getRuntime().maxMemory() / 1024 / 1024 + " MB");
VelocityEngine engine = new VelocityEngine();
engine.setProperty("resource.loaders", "classpath");
engine.setProperty("resource.loader.classpath.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.setProperty("resource.default_encoding", "UTF-8");
engine.setProperty("velocimacro.library.path", "templates/macros.vm");
engine.init();
VelocityContext context = new VelocityContext();
context.put("name", "quzuo");
Map<String, Object> user = new HashMap<>();
user.put("name", "admin");
user.put("email", "admin@quzuo.net");
user.put("vip", true);
user.put("level", 3);
context.put("user", user);
List<Map<String, Object>> items = new ArrayList<>();
items.add(item("机械键盘", 299.0));
items.add(item("无线鼠标", 129.5));
items.add(item("显示器支架", 89.9));
context.put("items", items);
Template template = engine.getTemplate("templates/main.vm", "UTF-8");
StringWriter writer = new StringWriter();
template.merge(context, writer);
System.out.println("========== 渲染结果 ==========");
System.out.println(writer);
}
}
5.2 演示模板 main.vm
覆盖变量、条件、循环、文件引入与宏调用五类特性:
## 1. 变量引用
姓名:$name
正式引用:${name}
不存在的引用(普通):[$missingValue]
不存在的引用(静默):[$!missingValue]
对象属性:$user.name / $user.email
## 2. 条件指令
#if($user.vip)
VIP 等级:$user.level
#elseif($user.name == "admin")
普通管理员
#else
普通用户
#end
## 3. 循环指令
商品清单:
#foreach($item in $items)
$foreach.count. $item.name —— 单价 $item.price 元
#end
共 $items.size() 件商品
## 4. include 与 parse
#include("templates/header.vm")
#parse("templates/footer.vm")
## 5. 宏调用
#greet("张三")
#greet("李四")
配套文件 templates/header.vm:
==== 系统公告:本报告由 Velocity 模板引擎渲染 ====
templates/footer.vm(演示 #set):
#set($copyright = "---- 2026 quzuo.net 版权所有 ----")
$copyright
templates/macros.vm(外部宏库):
#macro(greet $who)
你好,$who!欢迎使用 Apache Velocity。
#end
5.3 运行回显
cd /root/velocity-demo && LANG=C.UTF-8 mvn -q exec:java -Dexec.mainClass=com.example.VelocityDemo
实测回显:
JVM maxMemory = 8016 MB
========== 渲染结果 ==========
姓名:quzuo
正式引用:quzuo
不存在的引用(普通):[$missingValue]
不存在的引用(静默):[]
对象属性:admin / admin@quzuo.net
VIP 等级:3
商品清单:
1. 机械键盘 —— 单价 299.0 元
2. 无线鼠标 —— 单价 129.5 元
3. 显示器支架 —— 单价 89.9 元
共 3 件商品
==== 系统公告:本报告由 Velocity 模板引擎渲染 ====
---- 2026 quzuo.net 版权所有 ----
你好,张三!欢迎使用 Apache Velocity。
你好,李四!欢迎使用 Apache Velocity。
回显要点:$missingValue 原样保留、$!missingValue 输出为空、外部宏库经 velocimacro.library.path 加载后 #greet 正常展开;#include 原样并入文件内容,#parse 则先解析执行(#set 生效)。
六、Toolbox 工具箱集成
6.1 注册工具
velocity-tools 3.1 的 ToolManager 默认不加载任何工具,需用 EasyFactoryConfiguration 显式注册:
package com.example;
import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.config.EasyFactoryConfiguration;
import org.apache.velocity.tools.generic.DateTool;
import org.apache.velocity.tools.generic.EscapeTool;
import org.apache.velocity.tools.generic.MathTool;
import java.io.StringWriter;
import java.util.Date;
public class ToolboxDemo {
public static void main(String[] args) {
VelocityEngine engine = new VelocityEngine();
engine.setProperty("resource.loaders", "classpath");
engine.setProperty("resource.loader.classpath.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.setProperty("resource.default_encoding", "UTF-8");
engine.init();
EasyFactoryConfiguration config = new EasyFactoryConfiguration(false);
config.toolbox("application")
.tool("date", DateTool.class)
.tool("math", MathTool.class)
.tool("esc", EscapeTool.class);
ToolManager manager = new ToolManager(false, false);
manager.configure(config);
Context context = manager.createContext();
context.put("now", new Date());
Template template = engine.getTemplate("templates/toolbox.vm", "UTF-8");
StringWriter writer = new StringWriter();
template.merge(context, writer);
System.out.println("========== Toolbox 渲染结果 ==========");
System.out.println(writer);
}
}
6.2 工具演示模板 toolbox.vm
日期格式化:$date.format('yyyy-MM-dd HH:mm:ss', $now)
数学运算:$math.add(19, 23) / $math.mul(6, 7)
HTML 转义:$esc.html('<b>hello & welcome</b>')
URL 编码:$esc.url('quzuo net')
6.3 运行回显
cd /root/velocity-demo && LANG=C.UTF-8 mvn -q exec:java -Dexec.mainClass=com.example.ToolboxDemo
实测回显:
========== Toolbox 渲染结果 ==========
日期格式化:2026-10-12 09:41:04
数学运算:42 / 42
HTML 转义:<b>hello & welcome</b>
URL 编码:quzuo+net
七、属性文件方式集成
配置项较多时改用 Properties 文件。创建 /etc/velocity-demo/velocity-demo.properties:
resource.loaders = classpath
resource.loader.classpath.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
resource.default_encoding = UTF-8
directive.foreach.max_loops = 2
velocimacro.library.path = templates/macros.vm
runtime.strict_mode.enable = true
加载方式:
Properties props = new Properties();
try (FileInputStream in = new FileInputStream("/etc/velocity-demo/velocity-demo.properties")) {
props.load(in);
}
VelocityEngine engine = new VelocityEngine();
engine.setProperties(props);
engine.init();
实测该配置下:foreach 上限 2 生效(3 条数据只渲染 2 条);严格模式生效,输出未定义引用抛 MethodInvocationException,消息形如 Variable $undefinedRef has not been set at templates/strict-ref.vm[line 1, column 12]。注意 runtime.log 在 2.4.1 已废弃,日志由 SLF4J 输出,落盘需配置日志绑定(如 -Dorg.slf4j.simpleLogger.logFile=/var/log/velocity/velocity-demo.log)。
八、JVM 堆内存配置与验证
承载 Velocity 的应用 JVM 建议堆上限 256 至 512MB 起步,按模板数量与并发调整:
- java 直跑:参数写在 java 命令后,
java -Xms256m -Xmx512m -cp 'target/classes:libs/*' com.example.VelocityDemo。 - mvn exec:java:参数写入 MAVEN_OPTS,
MAVEN_OPTS='-Xms256m -Xmx512m' mvn exec:java;插件 jvmArgs 对 exec:java 无效,实测默认运行 maxMemory 为 8016 MB,配置 MAVEN_OPTS 后为 512 MB。
验证实测(java -Xms256m -Xmx512m -XshowSettings:vm -version):
VM settings:
Min. Heap Size: 256.00M
Max. Heap Size: 512.00M
Using VM: OpenJDK 64-Bit Server VM
运行中进程采集(示例程序支持 --hold 保活便于采集):
jps -lv:
6914 com.example.VelocityDemo -Xms256m -Xmx512m
ps -ef:
root 6914 6853 13 09:27 ? 00:00:00 java -Xms256m -Xmx512m -cp target/classes:libs/* com.example.VelocityDemo --hold
九、注意事项
- velocity-engine-core 2.4.1 硬依赖 slf4j-api,缺失时引擎初始化抛 NoClassDefFoundError;只加 api 不加绑定则日志被吞,建议固定搭配 slf4j-simple。
- velocity-tools 3.1 不要用旧坐标 org.apache.velocity:velocity-tools:2.0 替代,旧包面向 Velocity 1.7。
- 模板正文不要出现裸露的指令词片段(如在说明文字里写 #if),否则触发词法解析异常 Lexical error。
- 修改 resources 下模板后需重新 mvn compile,运行时读取的是 target/classes 副本。
- exec:java 的 -Dexec.mainClass 会被 pom 中显式 mainClass 配置覆盖,pom 中不要写死主类。
十、常见问题
Q1:渲染结果里 $missingValue 为什么原样输出,$!missingValue 却是空? A:普通引用在变量未定义时保留原文便于暴露问题,静默引用 $! 前缀会输出空串,实测两者行为如 5.3 回显所示。这是定位模板变量错误的首要看点。
Q2:宏调用 #greet 被原样输出怎么排查? A:宏库未加载。确认 velocimacro.library.path 指向的文件存在于对应加载器路径下,且引擎初始化前完成设置;配置正确后实测宏正常展开。
Q3:ToolboxDemo 里模板引用 $date 原样输出是什么原因? A:ToolManager 默认构造不加载任何工具,必须像 6.1 那样用 EasyFactoryConfiguration 注册后 createContext,注册后 $date、$math、$esc 实测全部可用。
Q4:如何让未定义变量在测试阶段直接报错? A:开启严格模式 runtime.strict_mode.enable=true。实测输出位置未定义引用抛 MethodInvocationException 且带模板行列号,便于定位拼写错误;注意静默引用在严格模式下同样抛异常。
十一、参考链接
- Velocity 引擎开发者文档:https://velocity.apache.org/engine/2.4/developer-guide.html
- VTL 参考指南:https://velocity.apache.org/engine/2.4/vtl-reference.html
- velocity-tools 用户指南:https://velocity.apache.org/tools/3.1/
- velocity-engine-core 2.4.1(Maven Central):https://mvnrepository.com/artifact/org.apache.velocity/velocity-engine-core