Velocity 模板引擎:从入门到精通的完整指南
前言
在 Java Web 开发的世界里,模板引擎是连接后端逻辑和前端展示的重要桥梁。Velocity 作为 Apache 旗下的经典模板引擎,以其简洁的语法、出色的性能和灵活的扩展性,在企业级应用中占据着重要地位。今天,让我们深入探索 Velocity 的核心用法和最佳实践。
一、Velocity 是什么?
Velocity 是一个基于 Java 的模板引擎,它允许开发者使用简单的模板语言(VTL)来引用 Java 对象中的内容。简单来说,Velocity 就像是一个智能的文本处理器,它能够将数据和模板优雅地结合在一起。
核心优势:
- ✅ 语法简洁直观,学习成本低
- ✅ 与 Java 代码完全分离
- ✅ 性能优秀,支持缓存
- ✅ 扩展性强,支持自定义指令
- ✅ 应用广泛,生态成熟
二、快速上手:5 分钟入门
1. 添加依赖
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
2. 第一个 Velocity 程序
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.VelocityContext;
import java.io.StringWriter;
public class VelocityDemo {
public static void main(String[] args) {
// 初始化引擎
VelocityEngine engine = new VelocityEngine();
engine.init();
// 创建上下文
VelocityContext context = new VelocityContext();
context.put("name", "张三");
context.put("age", 28);
// 定义模板
String template = "你好,我是 $name,今年 $age 岁。";
// 渲染输出
StringWriter writer = new StringWriter();
engine.evaluate(context, writer, "demo", template);
System.out.println(writer.toString());
// 输出:你好,我是 张三,今年 28 岁。
}
}
三、核心语法全解析
1. 变量引用(Reference)
## 基本引用
$name
${name}
## 属性引用
$user.name
$user.getName()
## 方法调用
$list.size()
$string.substring(0, 5)
## 静默引用(不存在时不报错)
$!name
$!{undefinedVar}
2. 指令(Directive)
#set - 变量赋值
#set($greeting = "Hello")
#set($num = 100)
#set($list = ["A", "B", "C"])
#set($map = {"key1": "value1", "key2": "value2"})
#if/#elseif/#else - 条件判断
#if($score >= 90)
优秀
#elseif($score >= 60)
及格
#else
不及格
#end
#foreach - 循环遍历
#foreach($item in $list)
${velocityCount}. $item
#if($velocityHasNext),#end
#end
## 内置变量
## $velocityCount: 当前索引(从1开始)
## $velocityHasNext: 是否有下一个元素
#macro - 宏定义
#macro(sayHello $name)
<div class="greeting">Hello, $name!</div>
#end
## 使用宏
#sayHello("World")
3. 注释
## 单行注释
#*
多行注释
可以跨越多行
*#
#**
文档注释
类似 JavaDoc
*#
四、最佳实践精选
实践 1:用户列表展示
Java 代码:
public class User {
private String name;
private int age;
private String email;
// getter/setter...
}
// 准备数据
List<User> users = Arrays.asList(
new User("张三", 25, "zhangsan@example.com"),
new User("李四", 30, "lisi@example.com"),
new User("王五", 28, "wangwu@example.com")
);
VelocityContext context = new VelocityContext();
context.put("users", users);
context.put("title", "用户列表");
Velocity 模板:
<!DOCTYPE html>
<html>
<head>
<title>$title</title>
<style>
.user-card {
border: 1px solid #ddd;
padding: 15px;
margin: 10px 0;
border-radius: 5px;
}
</style>
</head>
<body>
<h1>$title</h1>
#if($users && $users.size() > 0)
<p>共有 $users.size() 位用户</p>
#foreach($user in $users)
<div class="user-card">
<h3>${velocityCount}. $user.name</h3>
<p>年龄:$user.age</p>
<p>邮箱:$user.email</p>
</div>
#end
#else
<p>暂无用户数据</p>
#end
</body>
</html>
实践 2:邮件模板生成器
// 邮件上下文
VelocityContext context = new VelocityContext();
context.put("username", "张三");
context.put("orderNo", "2025101301");
context.put("products", productList);
context.put("totalPrice", 2999.00);
context.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
邮件模板:
#macro(formatPrice $price)
¥${price}
#end
尊敬的 $username:
您的订单已确认!
订单编号:$orderNo
下单时间:$date
订单详情:
#foreach($product in $products)
${velocityCount}. ${product.name} x ${product.quantity} #formatPrice($product.price)
#end
订单总金额:#formatPrice($totalPrice)
感谢您的购买!
---
本邮件由系统自动发送,请勿回复。
实践 3:JSON 字符串数组转对象数组
在实际开发中,我们经常遇到需要将转义的 JSON 字符串数组转换为真正的 JSON 对象数组的场景。
场景描述:
数据库或接口返回的是转义后的 JSON 字符串数组:
"[\"{\\\"name\\\":\\\"zhangsan\\\",\\\"age\\\":18}\",\"{\\\"name\\\":\\\"lisi\\\",\\\"age\\\":19}\"]"
需要转换为标准的 JSON 对象数组:
[{"name":"zhangsan","age":18},{"name":"lisi","age":19}]
Java 代码:
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONArray;
// 准备数据
String jsonStr = "[\"{\\\"name\\\":\\\"zhangsan\\\",\\\"age\\\":18}\",\"{\\\"name\\\":\\\"lisi\\\",\\\"age\\\":19}\"]";
// 将 JSONObject 和 JSONArray 工具类注入到上下文
VelocityContext context = new VelocityContext();
context.put("jsonStr", jsonStr);
context.put("JSONObject", JSONObject.class);
context.put("JSONArray", JSONArray.class);
Velocity 模板方案一(推荐):
## 解析外层数组
#set($outerArray = $JSONArray.parseArray($jsonStr))
## 创建新的结果数组(使用 Velocity 原生数组)
#set($resultArray = [])
## 遍历并解析每个字符串为对象
#foreach($itemStr in $outerArray)
#set($jsonObj = $JSONObject.parseObject($itemStr))
#set($add = $resultArray.add($jsonObj))
#end
## 直接返回处理后的结果
$resultArray
输出结果:
[{"name":"zhangsan","age":18}, {"name":"lisi","age":19}]
Velocity 模板方案二(一行搞定):
如果你想更简洁,可以先在 Java 中创建工具方法:
public class JsonTool {
/**
* 将字符串数组转换为对象数组
*/
public JSONArray convertStringArrayToObjectArray(String jsonStr) {
JSONArray outerArray = JSONArray.parseArray(jsonStr);
JSONArray resultArray = new JSONArray();
for (Object item : outerArray) {
JSONObject jsonObj = JSONObject.parseObject(item.toString());
resultArray.add(jsonObj);
}
return resultArray;
}
}
// 注入工具类
context.put("jsonTool", new JsonTool());
使用工具类的模板:
#set($result = $jsonTool.convertStringArrayToObjectArray($jsonStr))
$result.toJSONString()
实际应用场景:
<!DOCTYPE html>
<html>
<head>
<title>用户信息展示</title>
</head>
<body>
<h1>用户列表</h1>
## 转换数据
#set($outerArray = $JSONArray.parseArray($jsonStr))
#set($users = [])
#foreach($itemStr in $outerArray)
#set($user = $JSONObject.parseObject($itemStr))
#set($add = $users.add($user))
#end
## 展示数据
<table border="1">
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
</tr>
#foreach($user in $users)
<tr>
<td>$velocityCount</td>
<td>$user.getString("name")</td>
<td>$user.getInteger("age")</td>
</tr>
#end
</table>
## 输出为标准 JSON
#set($jsonResult = $JSONArray.parseArray($users.toString()))
<script>
const users = $jsonResult.toJSONString();
console.log('转换后的数据:', users);
</script>
</body>
</html>
关键技巧总结:
- ✅ 使用
$JSONArray.parseArray()
解析外层数组 - ✅ 使用
#set($array = [])
创建 Velocity 原生数组 - ✅ 使用
$JSONObject.parseObject()
解析每个字符串元素 - ✅ 通过
$array.add()
添加解析后的对象 - ✅ 最后转换为 JSONArray 输出标准 JSON 格式
- ✅ 可以封装工具类简化模板代码
实践 4:代码生成器
package ${package};
import java.io.Serializable;
import lombok.Data;
/**
* ${tableComment}
* @author Auto Generator
* @date ${date}
*/
@Data
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
#foreach($field in $fields)
/**
* ${field.comment}
*/
private ${field.type} ${field.name};
#end
}
五、进阶技巧
1. 工具类注入
// 自定义工具类
public class DateTool {
public String format(Date date, String pattern) {
return new SimpleDateFormat(pattern).format(date);
}
public Date now() {
return new Date();
}
}
// 注入到上下文
context.put("dateTool", new DateTool());
当前时间:$dateTool.format($dateTool.now(), "yyyy-MM-dd HH:mm:ss")
2. 自定义指令
public class UpperCaseDirective extends Directive {
@Override
public String getName() {
return "uppercase";
}
@Override
public int getType() {
return LINE;
}
@Override
public boolean render(InternalContextAdapter context,
Writer writer,
Node node) throws IOException {
String text = node.jjtGetChild(0).value(context).toString();
writer.write(text.toUpperCase());
return true;
}
}
#uppercase("hello world")
## 输出:HELLO WORLD
3. 模板缓存配置
Properties props = new Properties();
props.setProperty("resource.loader", "file");
props.setProperty("file.resource.loader.path", "/templates");
props.setProperty("file.resource.loader.cache", "true");
props.setProperty("file.resource.loader.modificationCheckInterval", "2");
VelocityEngine engine = new VelocityEngine(props);
engine.init();
六、常见陷阱与避坑指南
陷阱 1:空值处理
❌ 错误写法
$user.name
✅ 正确写法
$!user.name
#if($user && $user.name)$user.name#end
陷阱 2:字符串拼接
❌ 错误写法
$name+$age ## 输出:张三+25
✅ 正确写法
${name}${age} ## 输出:张三25
#set($result = "$name$age")
陷阱 3:循环中的性能
❌ 低效写法
#foreach($item in $list)
#if($item.price > 100) ## 每次都计算
...
#end
#end
✅ 优化写法
#set($expensiveItems = $tool.filter($list, "price", 100))
#foreach($item in $expensiveItems)
...
#end
七、实战场景汇总
场景 | 使用示例 | 优势 |
---|---|---|
邮件通知 | 订单确认、活动推送 | 模板统一管理 |
报表生成 | PDF、Excel 报表 | 格式灵活可控 |
代码生成 | Entity、DAO、Service | 提升开发效率 |
静态页面 | 商品详情、活动页 | SEO 友好 |
配置文件 | XML、JSON 生成 | 动态配置 |
八、学习建议
新手阶段(1-2天):
- 掌握基本语法:变量、指令、注释
- 完成简单模板练习
- 理解上下文概念
进阶阶段(1周):
- 掌握宏定义和工具类
- 学习配置和优化
- 实现一个小项目
高级阶段(持续):
- 自定义指令开发
- 性能优化实践
- 源码阅读理解
九、完整示例代码
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import java.io.StringWriter;
import java.util.Properties;
public class VelocityBestPractice {
private static VelocityEngine engine;
static {
Properties props = new Properties();
props.setProperty("resource.loader", "class");
props.setProperty("class.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine = new VelocityEngine(props);
engine.init();
}
public static String render(String templatePath, Map<String, Object> data) {
VelocityContext context = new VelocityContext(data);
Template template = engine.getTemplate(templatePath, "UTF-8");
StringWriter writer = new StringWriter();
template.merge(context, writer);
return writer.toString();
}
}
总结
Velocity 虽然不是最新的技术,但它简洁、稳定、高效的特性让它在许多场景下仍然是最佳选择。掌握 Velocity,你将拥有一个强大的文本处理利器。
记住这些关键点:
- 简洁优于复杂
- 始终处理空值
- 合理使用缓存
- 善用工具类和宏
- 保持模板可读性
推荐资源
- 官方文档:http://velocity.apache.org/
- GitHub:https://github.com/apache/velocity-engine
- 在线测试:可使用在线 Velocity 编辑器快速验证语法
喜欢这篇文章吗?
- 👍 点个「在看」,让更多人看到
- 🔗 转发给需要的朋友
- 💬 留言分享你的 Velocity 使用心得
Q.E.D.