Apache Velocity 快速入门与集成示例

Apache Velocity 2.4.1 快速入门:从 archetype 生成 Maven 工程到变量、条件、循环、宏与 Toolbox 工具箱集成,逐步附编译运行真实回显与 JVM 堆内存验证方法,可照抄复现。

Apache VelocityApache Velocity2026/10/12

Apache Velocity 快速入门与集成示例

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS(CentOS 8 实测行为一致)
登录账号root
JDKOpenJDK 17.0.20.1
Maven3.6.3
组件velocity-engine-core 2.4.1
辅助依赖slf4j-simple 1.7.36、velocity-tools-generic 3.1
工程目录/root/velocity-quickstart、/root/velocity-demo

二、前置条件

  1. 已安装 JDK 17 与 Maven 3.6.3,mvn -v 可正常回显。
  2. 服务器可访问 Maven Central。
  3. 会话 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 转义:&lt;b&gt;hello &amp; welcome&lt;/b&gt;
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

九、注意事项

  1. velocity-engine-core 2.4.1 硬依赖 slf4j-api,缺失时引擎初始化抛 NoClassDefFoundError;只加 api 不加绑定则日志被吞,建议固定搭配 slf4j-simple。
  2. velocity-tools 3.1 不要用旧坐标 org.apache.velocity:velocity-tools:2.0 替代,旧包面向 Velocity 1.7。
  3. 模板正文不要出现裸露的指令词片段(如在说明文字里写 #if),否则触发词法解析异常 Lexical error。
  4. 修改 resources 下模板后需重新 mvn compile,运行时读取的是 target/classes 副本。
  5. 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 且带模板行列号,便于定位拼写错误;注意静默引用在严格模式下同样抛异常。

十一、参考链接

  1. Velocity 引擎开发者文档:https://velocity.apache.org/engine/2.4/developer-guide.html
  2. VTL 参考指南:https://velocity.apache.org/engine/2.4/vtl-reference.html
  3. velocity-tools 用户指南:https://velocity.apache.org/tools/3.1/
  4. velocity-engine-core 2.4.1(Maven Central):https://mvnrepository.com/artifact/org.apache.velocity/velocity-engine-core