Apache Velocity 教程:从入门到精通
目录
- 什么是 Apache Velocity?
- 核心概念:模板、上下文、合并
- 第一个 Velocity 程序:Hello World!
- Velocity 模板语言 (VTL) 详解
- 实践项目:生成动态 HTML 页面
- 最佳实践和注意事项
- 总结与进阶
- 学习资源
什么是 Apache Velocity?
Apache Velocity 是一个基于 Java 的模板引擎,它允许你使用一个简单的、模板化的语言来引用 Java 代码中定义的对象,从而生成文本输出(如 HTML、XML、SQL、电子邮件等)。

核心思想:
- 关注点分离:将 Java 业务逻辑代码与页面展示代码(模板)分离开,开发者专注于后端逻辑,设计师可以专注于前端页面,而互不干扰。
- 模板驱动:模板文件(
.vm)是纯文本,里面混合了 Velocity 指令(以 开头)和占位符(以 开头)。 - 简单易学:VTL 语法非常直观,对于有编程基础的人来说很容易上手。
主要用途:
- Web 开发:作为 MVC 模型中的视图层,生成动态网页。
- 代码生成:根据模板生成源代码(如 Java、XML 配置文件)。
- 电子邮件模板:发送个性化的 HTML 电子邮件。
- 静态页面生成:将动态网站的内容预先渲染成静态 HTML,以提高访问速度。
核心概念:模板、上下文、合并
理解 Velocity 的工作流程,需要掌握三个核心概念:
-
模板
(图片来源网络,侵删)- 一个包含静态文本和 VTL 指令的文件(通常以
.vm为后缀)。 hello.vm可以包含"Hello, $name!"。
- 一个包含静态文本和 VTL 指令的文件(通常以
-
上下文
- 一个 Java 对象,通常是一个
java.util.Map或VelocityContext的实例。 - 它充当了模板和 Java 代码之间的桥梁,存储了模板中需要引用的所有变量和对象。
- 你可以将
name变量放入上下文,并赋值为 "World"。
- 一个 Java 对象,通常是一个
-
合并
- 这是 Velocity 引擎的核心操作,引擎会读取模板文件,解析其中的 VTL 指令和占位符,然后从上下文中查找对应的值,替换掉占位符,最终生成最终的输出文本。
- 流程图:
[Java 代码] --> 创建并填充 --> [VelocityContext] --> 交由 --> [Velocity Engine] (提供数据) (数据容器) (处理器) | V 读取并处理 --> [Template File (.vm)] --> 生成 --> [Output Text] (模板) (最终结果)
第一个 Velocity 程序:Hello World!
让我们通过一个最简单的例子来感受一下 Velocity 的工作方式。
步骤 1:添加 Maven 依赖
如果你使用 Maven,在 pom.xml 中添加 Velocity 的核心依赖:

<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version> <!-- 建议使用较新版本 -->
</dependency>
步骤 2:创建模板文件
在 src/main/resources 目录下创建一个名为 hello.vm 的文件:
hello.vm
Hello, $name!
Welcome to the world of Velocity!
步骤 3:编写 Java 代码
创建一个 Java 类来加载模板、设置上下文并渲染输出。
HelloWorldExample.java
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import java.io.StringWriter;
import java.util.Properties;
public class HelloWorldExample {
public static void main(String[] args) {
// 1. 初始化 VelocityEngine
// Velocity 引擎需要一个配置文件来设置其行为
Properties props = new Properties();
props.setProperty("resource.loader", "class");
props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
VelocityEngine engine = new VelocityEngine(props);
// 2. 创建 VelocityContext 并放入数据
VelocityContext context = new VelocityContext();
context.put("name", "Velocity User"); // 将 "name" 变量放入上下文
// 3. 加载模板
// 模板文件位于 classpath 根目录下
Template template = engine.getTemplate("hello.vm");
// 4. 合并模板与上下文,生成输出
StringWriter writer = new StringWriter();
template.merge(context, writer);
// 5. 输出结果
System.out.println(writer.toString());
}
}
运行结果
当你运行 HelloWorldExample.java 时,控制台会输出:
Hello, Velocity User!
Welcome to the world of Velocity!
解释:
- 我们配置了 Velocity 引擎,让它从 classpath(类路径)加载模板文件。
- 创建了一个
VelocityContext,并用context.put("key", "value")的方式向其中存入了一个名为name的变量。 engine.getTemplate("hello.vm")从 classpath 加载了我们的模板。template.merge(context, writer)是核心步骤,引擎将模板中的$name替换为上下文中的 "Velocity User",并将结果写入StringWriter。- 我们从
StringWriter中取出并打印最终生成的文本。
Velocity 模板语言 (VTL) 详解
VTL 是 Velocity 的灵魂,它简单而强大。
1. 注释
Velocity 提供了两种注释方式:
- 单行注释:以 开头,直到行尾。
## 这是一个单行注释,不会出现在输出中
- 多行/块注释:以 开头,以
#* 这是一个多行注释。 可以跨越多行。 也不会被渲染。 *#
2. 引用
引用用于获取上下文中存储的值。
变量引用 ($variable)
直接引用一个简单的变量。
#set( $name = "John Doe" ) Hello, $name!
输出:Hello, John Doe!
如果变量不存在,Velocity 默认会输出 $!variable 的形式(见下文转义部分)。
属性引用 ($object.property)
引用一个对象的属性(通过 getter 方法)。
假设 Java 代码中有:
User user = new User();
user.setName("Alice");
user.setEmail("alice@example.com");
context.put("user", user);
模板中:
User's Name: $user.Name User's Email: $user.email
输出:
User's Name: Alice
User's Email: alice@example.com
注意:Velocity 的属性访问是大小写不敏感的,并且会自动调用对应的 getter 方法(如 getName())。
方法引用 ($object.method())
调用一个对象的方法。
假设 Java 代码中有:
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
context.put("fruits", fruits);
模板中:
The first fruit is: $fruits.get(0) The list has $fruits.size() items.
输出:
The first fruit is: Apple
The list has 3 items.
3. 指令
指令以 开头,用于控制模板的逻辑流程。
#set - 赋值
为变量赋值。
#set( $name = "Velocity" ) #set( $price = 19.99 ) #set( $inStock = true ) #set( $colors = ["red", "green", "blue"] ) Name: $name Price: $price In Stock: $inStock First Color: $colors.get(0)
注意:
- 变量赋值值的两边不能有空格,
#set( $name = "Velocity" )是正确的,#set( $name = "Velocity" )是错误的。 - 可以引用其他已设置的变量:
#set( $message = "Hello, $name!" )
#if / #elseif / #else - 条件判断
根据条件渲染不同的内容。
#if( $isadmin )
Welcome, Administrator!
#elseif( $isuser )
Welcome, User!
#else
Welcome, Guest!
#end
条件判断可以包含 && (and), (or), (not) 等逻辑运算符:
#if( $age > 18 && $age < 65 )
You are an adult.
#else
You are a minor or a senior.
#end
#foreach - 循环
遍历一个 Collection 或 Map。
遍历 List/Array: 假设 Java 代码:
List<String> users = Arrays.asList("Peter", "Paul", "Mary");
context.put("users", users);
模板:
<h2>User List:</h2>
<ul>
#foreach( $user in $users )
<li>$user</li>
#end
</ul>
输出:
<h2>User List:</h2>
<ul>
<li>Peter</li>
<li>Paul</li>
<li>Mary</li>
</ul>
循环变量:
在 #foreach 循环中,Velocity 会自动提供两个有用的变量:
$foreach.count: 当前是第几次循环(从 1 开始)。$foreach.hasNext: 是否还有下一个元素。
#foreach( $user in $users )
$foreach.count. $user#if( $foreach.hasNext ), #end
#end
输出:Peter, 2. Paul, 3. Mary
遍历 Map: 假设 Java 代码:
Map<String, String> capitals = new HashMap<>();
capitals.put("USA", "Washington D.C.");
capitals.put("UK", "London");
capitals.put("France", "Paris");
context.put("capitals", capitals);
模板:
#foreach( $country in $capitals.keySet() )
The capital of $country is $capitals.get($country).
#end
输出:
The capital of USA is Washington D.C..
The capital of UK is London.
The capital of France is Paris.
#include / #parse - 包含和解析其他模板
这两个指令都可以将另一个模板的内容合并到当前模板中。
#include
- 作用:简单地将指定文件的内容原样插入到当前位置。
- 特点:被包含的文件不会被 Velocity 引擎重新解析,其中的 VTL 指令会作为普通文本输出。
- 用途:包含静态的 HTML 片段,如页脚、版权信息等。
#parse
- 作用:将指定文件的内容插入,并且会将其作为 Velocity 模板进行解析。
- 特点:被解析的文件中的 VTL 指令会被执行。
- 注意:
#parse可以嵌套,但要注意防止无限递归。 - 用途:包含可重用的模板片段,如导航栏、头部等,这些片段可能包含自己的逻辑。
示例:
假设 在 宏可以定义一段可重用的代码片段,类似于函数。 语法: 示例:
定义一个显示按钮的宏: 在模板中调用宏: 输出: 字符串:用双引号 或单引号 括起来,双引号内的变量引用( 转义符 输出: 输出: 安静引用符 : 让我们构建一个更实际的项目:生成一个包含用户列表的 HTML 页面。 创建一个 User.java 在 创建一个 HtmlGenerator.java 运行 通过本教程,你已经掌握了 Apache Velocity 的基本用法,包括其核心概念、VTL 语法以及一个完整的实践项目,Velocity 是一个轻量级、高效的模板引擎,特别适合于需要严格分离逻辑和展示的场景。 进阶方向:header.vm
<div id="header">
<h1>My Website</h1>
</div>
main.vm 中使用:<html>
<head><title>Main Page</title></head>
<body>
#parse("header.vm") <-- 会解析 header.vm
<p>This is the main content.</p>
#include("footer.html") <-- footer.html 的内容会原样插入
</body>
</html>
#macro - 宏定义#macro( 宏名 [参数1, 参数2, ...])
... 宏体内容 ...
#end
#macro( renderButton $text $color )
<button style="background-color: $color; color: white; padding: 10px 20px; border: none; border-radius: 5px;">
$text
</button>
#end
#renderButton("Click Me", "blue")
#renderButton("Submit", "green")
<button style="background-color: blue; color: white; padding: 10px 20px; border: none; border-radius: 5px;">
Click Me
</button>
<button style="background-color: green; color: white; padding: 10px 20px; border: none; border-radius: 5px;">
Submit
</button>
4. 字面值和转义
$var)会被解析,单引号内的则不会。#set( $name = "World" )
Double quotes: Hello, $name! <-- 输出: Hello, World!
Single quotes: Hello, $name! <-- 输出: Hello, $name!
\:
$100 而不是变量 100,可以使用转义符 \。The price is \$100.
The price is $100.\ 可以用来连接两行文本为一行。This is a long line of text \
that is actually on one line.
This is a long line of text that is actually on one line.
$!variable 可以避免在变量不存在时输出 $variable 这个字符串本身。$!variable 什么都不输出。$!variable 和 $variable 效果一样。## 假设 $user.name 不存在
Name: $user.name <-- 输出: Name: $user.name
Name: $!user.name <-- 输出: Name:
实践项目:生成动态 HTML 页面
步骤 1:准备数据模型
User 类。public class User {
private String name;
private int age;
private String email;
// 构造函数、Getter 和 Setter
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
}
步骤 2:创建模板
src/main/resources 目录下创建 users.vm。users.vm<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">User List</title>
<style>
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #dddddd; text-align: left; padding: 8px; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>Our Users</h1>
<table>
<tr>
<th>Name</th>
<th>Age</th>
<th>Email</th>
</tr>
#foreach( $user in $users )
<tr>
<td>$user.name</td>
<td>$user.age</td>
<td>$user.email</td>
</tr>
#end
</table>
</body>
</html>
步骤 3:编写 Java 主程序
HtmlGenerator 类来驱动整个过程。import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
public class HtmlGenerator {
public static void main(String[] args) {
// 1. 初始化引擎
VelocityEngine engine = new VelocityEngine();
engine.setProperty("resource.loader", "class");
engine.setProperty("class.resource.loader.class", ClasspathResourceLoader.class.getName());
engine.init();
// 2. 准备数据
List<User> userList = new ArrayList<>();
userList.add(new User("Alice", 30, "alice@example.com"));
userList.add(new User("Bob", 25, "bob@example.com"));
userList.add(new User("Charlie", 35, "charlie@example.com"));
// 3. 创建上下文并放入数据
VelocityContext context = new VelocityContext();
context.put("users", userList);
// 4. 加载模板
Template template = engine.getTemplate("users.vm");
// 5. 合并并生成输出
StringWriter writer = new StringWriter();
template.merge(context, writer);
// 6. 输出或保存结果
System.out.println("--- Generated HTML ---");
System.out.println(writer.toString());
// 在实际应用中,你可以将 writer.toString() 写入一个文件
// Files.write(Paths.get("users.html"), writer.toString().getBytes());
}
}
运行结果
HtmlGenerator.java,你将在控制台看到完整的 HTML 代码:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">User List</title>
<style>
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #dddddd; text-align: left; padding: 8px; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>Our Users</h1>
<table>
<tr>
<th>Name</th>
<th>Age</th>
<th>Email</th>
</tr>
<tr>
<td>Alice</td>
<td>30</td>
<td>alice@example.com</td>
</tr>
<tr>
<td>Bob</td>
<td>25</td>
<td>bob@example.com</td>
</tr>
<tr>
<td>Charlie</td>
<td>35</td>
<td>charlie@example.com</td>
</tr>
</table>
</body>
</html>
最佳实践和注意事项
$obj.property 这样的字符串。#macro 来封装,提高复用性。
VelocityEngine 是线程安全的,可以全局共享,避免在每次请求中都重新创建。Template 对象也可以被缓存起来,以提高性能。VelocityEngineUtils 来简化代码。EscapeTool 等工具。
总结与进阶
Directive 和 Toolbox 接口来创建自己的 VTL 指令和工具类,扩展 Velocity 的功能。EscapeTool, MathTool, DateTool 等),可以直接在模板中使用,简化开发。
学习资源
