Velocity 模板引擎:从入门到精通的完整指南

前言

在 Java Web 开发的世界里,模板引擎是连接后端逻辑和前端展示的重要桥梁。Velocity 作为 Apache 旗下的经典模板引擎,以其简洁的语法、出色的性能和灵活的扩展性,在企业级应用中占据着重要地位。今天,让我们深入探索 Velocity 的核心用法和最佳实践。

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 岁。
    }
}

三、核心语法全解析

Velocity 核心语法

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 对象数组的场景。

场景描述:
数据库或接口返回的是转义后的 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>

关键技巧总结:

  1. ✅ 使用 $JSONArray.parseArray() 解析外层数组
  2. ✅ 使用 #set($array = []) 创建 Velocity 原生数组
  3. ✅ 使用 $JSONObject.parseObject() 解析每个字符串元素
  4. ✅ 通过 $array.add() 添加解析后的对象
  5. ✅ 最后转换为 JSONArray 输出标准 JSON 格式
  6. ✅ 可以封装工具类简化模板代码

实践 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,你将拥有一个强大的文本处理利器。

记住这些关键点:

  • 简洁优于复杂
  • 始终处理空值
  • 合理使用缓存
  • 善用工具类和宏
  • 保持模板可读性

推荐资源


喜欢这篇文章吗?

  • 👍 点个「在看」,让更多人看到
  • 🔗 转发给需要的朋友
  • 💬 留言分享你的 Velocity 使用心得

Q.E.D.


寻门而入,破门而出