2. 概述

本文将聚焦 Mustache 模板引擎,并使用其 Java API 生成动态 HTML 内容。

Mustache 是一个无逻辑模板引擎,用于创建 HTML、配置文件等动态内容。

3. 简介

简单来说,该引擎被归类为"无逻辑",因为它不支持 if-else 语句或 for 循环等控制结构。

Mustache 模板由被 {{ }} 包围的标签名组成(因其形似胡子而得名),并由包含模板数据的模型对象支持。

4. Maven 依赖

Mustache 模板的编译和执行支持多种语言(客户端和服务器端)。要在 Java 中处理模板,我们使用其 Java 库,可通过 Maven 依赖添加:

Java 8+:

<dependency>
    <groupId>com.github.spullara.mustache.java</groupId>
    <artifactId>compiler</artifactId>
    <version>0.9.4</version>
</dependency>

Java 6/7:

<dependency>
    <groupId>com.github.spullara.mustache.java</groupId>
    <artifactId>compiler</artifactId>
    <version>0.8.18</version>
</dependency>

⚠️ 最新版本可在 Maven 中央仓库 查询。

5. 使用

我们通过一个简单场景演示:

  1. 编写简单模板
  2. 使用 Java API 编译模板
  3. 提供必要数据执行模板

5.1 一个简单的 Mustache 模板

创建一个显示待办任务详情的模板:

<h2>{{title}}</h2>
<small>创建于 {{createdOn}}</small>
<p>{{text}}</p>

花括号 {{}} 内的字段可以是:

  • Java 类的方法或属性
  • Map 对象的键

5.2 编译 Mustache 模板

编译模板的代码如下:

MustacheFactory mf = new DefaultMustacheFactory();
Mustache m = mf.compile("todo.mustache");

MustacheFactory 会在类路径中查找模板。本例中,我们将 todo.mustache 放在 src/main/resources 下。

5.3 执行 Mustache 模板

提供给模板的数据是 Todo 类的实例:

public class Todo {
    private String title;
    private String text;
    private boolean done;
    private Date createdOn;
    private Date completedOn;
    
    // 构造函数、getter 和 setter
}

执行编译后的模板生成 HTML:

Todo todo = new Todo("Todo 1", "Description");
StringWriter writer = new StringWriter();
m.execute(writer, todo).flush();
String html = writer.toString();

6. Mustache 区块与迭代

现在看看如何列出待办任务。迭代列表数据时,我们使用 Mustache 区块

区块是根据当前上下文中键的值重复渲染的代码块,格式如下:

{{#todo}}
<!-- 其他代码 -->
{{/todo}}

区块以 # 开始,以 / 结束,符号后跟随用于渲染区块的键。根据键值的不同,可能出现以下场景:

6.1 非空列表或非假值的区块

创建使用区块的模板 todo-section.mustache

{{#todo}}
<h2>{{title}}</h2>
<small>创建于 {{createdOn}}</small>
<p>{{text}}</p>
{{/todo}}

测试该模板:

@Test
public void givenTodoObject_whenGetHtml_thenSuccess() 
  throws IOException {
 
    Todo todo = new Todo("Todo 1", "Todo description");
    Mustache m = MustacheUtil.getMustacheFactory()
      .compile("todo.mustache");
    Map<String, Object> context = new HashMap<>();
    context.put("todo", todo);
 
    String expected = "<h2>Todo 1</h2>";
    assertThat(executeTemplate(m, todo)).contains(expected);
}

创建列出多个待办任务的模板 todos.mustache

{{#todos}}
<h2>{{title}}</h2>
{{/todos}}

使用该模板生成列表:

@Test
public void givenTodoList_whenGetHtml_thenSuccess() 
  throws IOException {
 
    Mustache m = MustacheUtil.getMustacheFactory()
      .compile("todos.mustache");
 
    List<Todo> todos = Arrays.asList(
      new Todo("Todo 1", "Todo description"),
      new Todo("Todo 2", "Todo description another"),
      new Todo("Todo 3", "Todo description another")
    );
    Map<String, Object> context = new HashMap<>();
    context.put("todos", todos);
 
    assertThat(executeTemplate(m, context))
      .contains("<h2>Todo 1</h2>")
      .contains("<h2>Todo 2</h2>")
      .contains("<h2>Todo 3</h2>");
}

6.2 空列表、假值或空值的区块

null 值测试 todo-section.mustache

@Test
public void givenNullTodoObject_whenGetHtml_thenEmptyHtml() 
  throws IOException {
    Mustache m = MustacheUtil.getMustacheFactory()
      .compile("todo-section.mustache");
    Map<String, Object> context = new HashMap<>();
    assertThat(executeTemplate(m, context)).isEmpty();
}

用空列表测试 todos.mustache

@Test
public void givenEmptyList_whenGetHtml_thenEmptyHtml() 
  throws IOException {
    Mustache m = MustacheUtil.getMustacheFactory()
      .compile("todos.mustache");
 
    Map<String, Object> context = new HashMap<>();
    assertThat(executeTemplate(m, context)).isEmpty();;
}

7. 反转区块

反转区块仅在键不存在、值为 false/null 或空列表时渲染一次。简单说,当区块不渲染时,反转区块就会渲染。

反转区块以 ^ 开始,以 / 结束:

{{#todos}}
<h2>{{title}}</h2>
{{/todos}}
{{^todos}}
<p>没有待办任务!</p>
{{/todos}}

当提供空列表时测试该模板:

@Test
public void givenEmptyList_whenGetHtmlUsingInvertedSection_thenHtml() 
  throws IOException {
 
    Mustache m = MustacheUtil.getMustacheFactory()
      .compile("todos-inverted-section.mustache");
  
    Map<String, Object> context = new HashMap<>();
    assertThat(executeTemplate(m, context).trim())
      .isEqualTo("<p>没有待办任务!</p>");
}

8. Lambda 表达式

Mustache 区块的键值可以是函数或 Lambda 表达式。此时,完整的 Lambda 表达式会被调用,并将区块内的文本作为参数传入。

看模板 todos-lambda.mustache

{{#todos}}
<h2>{{title}}{{#handleDone}}{{doneSince}}{{/handleDone}}</h2>
{{/todos}}

handleDone 键对应 Java 8 Lambda 表达式:

public Function<Object, Object> handleDone() {
    return (obj) -> done ? 
      String.format("<small>完成于 %s 分钟前</small>", obj) : "";
}

执行上述模板生成的 HTML:

<h2>Todo 1</h2>
<h2>Todo 2</h2>
<h2>Todo 3<small>完成于 5 分钟前</small></h2>

9. 总结

本文介绍了 Mustache 模板的基础用法,包括区块、反转区块和 Lambda 表达式,并通过 Java API 演示了如何编译和执行模板。

Mustache 还有更多高级特性值得探索:

  • ✅ 提供 Callable 作为值实现并发求值
  • ✅ 使用 DecoratedCollection 获取集合元素的 first/last/index 信息
  • invert API(根据文本和模板反推数据)

完整源代码可在 GitHub 获取。


原始标题:Introduction to Mustache | Baeldung