贝博恩创新科技网

Apache Velocity 教程,如何快速上手模板引擎?

Apache Velocity 教程:从入门到精通

目录

  1. 什么是 Apache Velocity?
  2. 核心概念:模板、上下文、合并
  3. 第一个 Velocity 程序:Hello World!
  4. Velocity 模板语言 (VTL) 详解
  5. 实践项目:生成动态 HTML 页面
  6. 最佳实践和注意事项
  7. 总结与进阶
  8. 学习资源

什么是 Apache Velocity?

Apache Velocity 是一个基于 Java 的模板引擎,它允许你使用一个简单的、模板化的语言来引用 Java 代码中定义的对象,从而生成文本输出(如 HTML、XML、SQL、电子邮件等)。

Apache Velocity 教程,如何快速上手模板引擎?-图1
(图片来源网络,侵删)

核心思想:

  • 关注点分离:将 Java 业务逻辑代码与页面展示代码(模板)分离开,开发者专注于后端逻辑,设计师可以专注于前端页面,而互不干扰。
  • 模板驱动:模板文件(.vm)是纯文本,里面混合了 Velocity 指令(以 开头)和占位符(以 开头)。
  • 简单易学:VTL 语法非常直观,对于有编程基础的人来说很容易上手。

主要用途:

  • Web 开发:作为 MVC 模型中的视图层,生成动态网页。
  • 代码生成:根据模板生成源代码(如 Java、XML 配置文件)。
  • 电子邮件模板:发送个性化的 HTML 电子邮件。
  • 静态页面生成:将动态网站的内容预先渲染成静态 HTML,以提高访问速度。

核心概念:模板、上下文、合并

理解 Velocity 的工作流程,需要掌握三个核心概念:

  1. 模板

    Apache Velocity 教程,如何快速上手模板引擎?-图2
    (图片来源网络,侵删)
    • 一个包含静态文本和 VTL 指令的文件(通常以 .vm 为后缀)。
    • hello.vm 可以包含 "Hello, $name!"
  2. 上下文

    • 一个 Java 对象,通常是一个 java.util.MapVelocityContext 的实例。
    • 它充当了模板和 Java 代码之间的桥梁,存储了模板中需要引用的所有变量和对象。
    • 你可以将 name 变量放入上下文,并赋值为 "World"。
  3. 合并

    • 这是 Velocity 引擎的核心操作,引擎会读取模板文件,解析其中的 VTL 指令和占位符,然后从上下文中查找对应的值,替换掉占位符,最终生成最终的输出文本。
    • 流程图
      [Java 代码]  -->  创建并填充 -->  [VelocityContext]  -->  交由 -->  [Velocity Engine]
        (提供数据)                                    (数据容器)                     (处理器)
                                                                                       |
                                                                                       V
                                                                 读取并处理 -->  [Template File (.vm)]  -->  生成 -->  [Output Text]
                                                                 (模板)                                  (最终结果)

第一个 Velocity 程序:Hello World!

让我们通过一个最简单的例子来感受一下 Velocity 的工作方式。

步骤 1:添加 Maven 依赖

如果你使用 Maven,在 pom.xml 中添加 Velocity 的核心依赖:

Apache Velocity 教程,如何快速上手模板引擎?-图3
(图片来源网络,侵删)
<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!

解释:

  1. 我们配置了 Velocity 引擎,让它从 classpath(类路径)加载模板文件。
  2. 创建了一个 VelocityContext,并用 context.put("key", "value") 的方式向其中存入了一个名为 name 的变量。
  3. engine.getTemplate("hello.vm") 从 classpath 加载了我们的模板。
  4. template.merge(context, writer) 是核心步骤,引擎将模板中的 $name 替换为上下文中的 "Velocity User",并将结果写入 StringWriter
  5. 我们从 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 - 循环

遍历一个 CollectionMap

遍历 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 可以嵌套,但要注意防止无限递归。
  • 用途:包含可重用的模板片段,如导航栏、头部等,这些片段可能包含自己的逻辑。

示例: 假设 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 页面

让我们构建一个更实际的项目:生成一个包含用户列表的 HTML 页面。

步骤 1:准备数据模型

创建一个 User 类。

User.java

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 类来驱动整个过程。

HtmlGenerator.java

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>

最佳实践和注意事项

  1. 关注点分离:严格遵守模板只负责展示,不负责业务逻辑的原则,不要在模板中写复杂的逻辑判断或循环。
  2. 模板可读性:保持模板的整洁和可读性,使用合理的缩进和注释,将复杂的逻辑拆分成多个小的模板或宏。
  3. 避免空指针:对于可能为 null 的对象,使用 来避免渲染出 $obj.property 这样的字符串。
  4. 使用宏:对于重复使用的 UI 片段(如按钮、表格行),强烈推荐使用 #macro 来封装,提高复用性。
  5. 性能考虑
    • VelocityEngine 是线程安全的,可以全局共享,避免在每次请求中都重新创建。
    • Template 对象也可以被缓存起来,以提高性能。
    • 在高并发场景下,可以考虑使用 Velocity 提供的工具类 VelocityEngineUtils 来简化代码。
  6. 安全性:Velocity 本身不提供 XSS(跨站脚本攻击)防护,如果模板输出内容来自用户输入,你需要自己对数据进行转义,或者使用 Velocity 的 EscapeTool 等工具。

总结与进阶

通过本教程,你已经掌握了 Apache Velocity 的基本用法,包括其核心概念、VTL 语法以及一个完整的实践项目,Velocity 是一个轻量级、高效的模板引擎,特别适合于需要严格分离逻辑和展示的场景。

进阶方向:

  • 集成到 Web 框架:Velocity 可以与 Spring MVC、Struts 等框架集成,作为其默认的视图技术。
  • 自定义指令和工具:你可以通过实现 DirectiveToolbox 接口来创建自己的 VTL 指令和工具类,扩展 Velocity 的功能。
  • 探索 Velocity Tools:Velocity 官方提供了一些常用的工具类(如 EscapeTool, MathTool, DateTool 等),可以直接在模板中使用,简化开发。

学习资源

分享:
扫描分享到社交APP
上一篇
下一篇