ZetCode

FreeMarker

最后修改于 2024 年 1 月 27 日

这是 FreeMarker Java 模板引擎的入门教程。我们介绍 FreeMarker 模板引擎,并创建几个控制台和 Web 应用程序。Maven 用于构建我们的示例。NetBeans 用于管理应用程序。

目录

FreeMarker 是 Java 编程语言的模板引擎。模板使用 FreeMarker 模板语言 (FTL) 编写。

FreeMarker 模板引擎

模板引擎将静态数据与动态数据组合以生成内容。模板是内容的中间表示形式;它指定如何生成输出。

模板引擎的优点是

按照惯例,FreeMarker 模板文件具有 .ftl 扩展名。

FreeMarker 不仅限于 HTML 页面的模板;它可用于生成电子邮件、配置文件、源代码等。

implementation 'org.freemarker:freemarker:2.3.31'

我们在 Gradle 项目中使用此 FreeMarker 依赖项。

FreeMarker 插值

插值是放在 ${ } 字符之间的表达式。FreeMarker 会将输出中的插值替换为花括号内表达式的实际值。

在以下示例中,我们使用 FreeMarker 模板文件生成简单的文本输出。

com/zetcode/FreeMarkerConsoleEx.java
package com.zetcode;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class FreeMarkerConsoleEx {

    public static void main(String[] args) throws IOException,
            TemplateException {

        var cfg = new Configuration(new Version("2.3.31"));

        cfg.setClassForTemplateLoading(FreeMarkerConsoleEx.class, "/views");
        cfg.setDefaultEncoding("UTF-8");

        Template template = cfg.getTemplate("test.ftlh");

        Map<String, Object> templateData = new HashMap<>();
        templateData.put("msg", "Today is a beautiful day");

        try (StringWriter out = new StringWriter()) {

            template.process(templateData, out);
            System.out.println(out.getBuffer().toString());

            out.flush();
        }
    }
}

该示例将一个简单的文本打印到控制台。最终文本由模板引擎处理。

var cfg = new Configuration(new Version("2.3.31"));

Configuration 用于设置 FreeMarker 设置;它将 FreeMarker 库的版本作为参数。

cfg.setClassForTemplateLoading(FreeMarkerConsoleEx.class, "/views");

setClassForTemplateLoading 设置将用于加载模板的类的方法。 模板位于 src/main/resources 目录的 views 子目录中。

Template template = cfg.getTemplate("test.ftlh");

使用 getTemplate 方法,我们检索 test.ftlh 模板文件。

Map<String, Object> templateData = new HashMap<>();
templateData.put("msg", "Today is a beautiful day");

创建数据模型。来自模型的数据将动态地放置到 FreeMarker 模板文件中。

try (StringWriter out = new StringWriter()) {

    template.process(templateData, out);
    System.out.println(out.getBuffer().toString());

    out.flush();
}

process 方法执行模板,使用提供的数据模型并将生成的输出写入提供的 writer。

resources/views/test.ftlh
The message is: ${msg}

test.ftlh 模板文件包含一个插值;它将被生成的字符串替换。

build.gradle
version '1.0'

apply plugin: 'java'
apply plugin: 'application'

sourceCompatibility = 17

mainClassName = "com.zetcode.FreeMarkerConsoleEx"

repositories {
    mavenCentral()
}

dependencies {

    implementation 'org.freemarker:freemarker:2.3.31'
}

这是 Gradle 构建文件。

$ gradle run -q
The message is: Today is a beautiful day

FreeMarker list 指令

#list 指令列出数据的集合。

下一个示例生成汽车列表。

com/zetcode/Car.java
package com.zetcode;

public class Car {

    private String name;
    private int price;

    public Car() {
    }

    public Car(String name, int price) {

        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

我们有一个 Car bean。 它有两个属性:名称和价格。

com/zetcode/FreeMarkerConsoleEx.java
package com.zetcode;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class FreeMarkerConsoleEx {

    public static void main(String[] args) throws IOException,
            TemplateException {

        var cfg = new Configuration(new Version("2.3.31"));

        cfg.setClassForTemplateLoading(FreeMarkerConsoleEx.class, "/views");
        cfg.setDefaultEncoding("UTF-8");

        Template template = cfg.getTemplate("test.ftlh");

        Map<String, Object> templateData = new HashMap<>();

        var c1 = new Car("Audi", 52642);
        var c2 = new Car("Volvo", 29000);
        var c3 = new Car("Skoda", 9000);

        var cars = new ArrayList<>();
        cars.add(c1);
        cars.add(c2);
        cars.add(c3);

        templateData.put("cars", cars);

        try (StringWriter out = new StringWriter()) {

            template.process(templateData, out);
            System.out.println(out.getBuffer().toString());

            out.flush();
        }
    }
}

此示例是一个 Java 控制台程序,它使用 FreeMarker 动态创建包含汽车列表的文本输出。

Map<String, Object> templateData = new HashMap<>();

var c1 = new Car("Audi", 52642);
var c2 = new Car("Volvo", 29000);
var c3 = new Car("Skoda", 9000);

var cars = new ArrayList<>();
cars.add(c1);
cars.add(c2);
cars.add(c3);

templateData.put("cars", cars);

在这里,我们创建一个 Car 对象列表并将其放入数据模型中。

resources/views/test.ftlh
<#list cars as car>
${car.name}: ${car.price}
</#list>

模板文件包含一个 #list 指令,该指令打印汽车对象的属性;使用点字符访问属性。

$ gradle run -q
Audi: 52,642
Volvo: 29,000
Skoda: 9,000

FreeMarker 指令

FreeMarker 指令是执行操作的特殊标记。有两种指令:内置指令和自定义指令。

<#assign> 标签创建一个新的纯变量。可以使用 ${} 构造访问它。变量在模板中创建。如果数据模型中存在同名变量,则模板变量会隐藏它。

assignment.ftl
<#assign name = "Robert">

His name is ${name}.

<#assign> 指令创建一个新的 name 变量。变量的值使用 ${name} 语法打印。

His name is Robert.

该示例打印此行。

可以使用 <#if><#elseif><#else> 指令完成模板部分的条件处理。

conditions.ftl
<#assign value = 4>

<#if value < 0>
  The number is negative
<#elseif value == 0>
  The number is zero
<#else>
  The number is positive
</#if>

该示例创建一个新的 value 变量,并使用条件指令来测试该值。

The number is positive

<#list> 指令用于遍历序列。

listing.ftl
<#assign colours = ["red", "green", "blue", "yellow"]>

<#list colours as col>
${col}
</#list>

在该示例中,我们向 colours 变量分配一个新的颜色名称序列。 <#list> 指令遍历该集合并打印每个项目。

red
green
blue
yellow

该示例给出此输出。

listing2.ftl
<#assign items = {"pens": 3, "cups": 2, "tables": 1}>

<#list items?values as v>
${v}
</#list>

<#list items?keys as k>
${k}
</#list>

在此示例中,我们创建一个哈希变量,并使用 <#list> 输出哈希的值和键。

3
2
1

pens
cups
tables

该示例给出此输出。

当我们使用对空白不敏感的格式(例如 HTML 或 XML)时,<#compress> 指令会删除多余的空白。

compressing.ftl
<#assign value="\t\tweather\n\n">

<#compress>
${value}
        Today is a wonderful day.
   1 2   3       4     5
</#compress>

我们有带有空格、制表符和换行符的文本。

weather
Today is a wonderful day.
1 2 3 4 5

该程序删除了所有多余的空白。

FreeMarker 与 Spark

在以下示例中,我们将把 FreeMarker 模板引擎集成到我们的 Spark 应用程序中。

build.gradle
src
└── main
    ├── java
    │   └── com
    │       └── zetcode
    │           └── SparkFreeMarkerEx.java
    └── resources
        └── views
            └── hello.ftlh

这是项目的目录结构。

build.gradle
apply plugin: 'java'
apply plugin: 'application'

archivesBaseName = "spark-freemarker"
version = '1.0'
mainClassName = "com.zetcode.SparkFreemarkerEx"

repositories {
  mavenCentral()
}

dependencies {
  implementation 'com.sparkjava:spark-core:2.9.4'
  implementation 'com.sparkjava:spark-template-freemarker:2.7.1'
  implementation 'org.slf4j:slf4j-simple:1.7.36'
}

在这里,我们有 Gradle 构建文件,其中包括 spark-template-freemarker 依赖项。

com/zetcode/SparkFreeMarkerEx.java
package com.zetcode;

import freemarker.template.Configuration;
import freemarker.template.Version;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import spark.ModelAndView;
import spark.Request;
import spark.Response;
import static spark.Spark.get;
import spark.template.freemarker.FreeMarkerEngine;

public class SparkFreemarkerEx {

    public static void main(String[] args) throws IOException {
        
        Configuration conf = new Configuration(new Version(2, 3, 26));
        conf.setClassForTemplateLoading(SparkFreemarkerEx.class, "/views");

        get("/hello/:name/", SparkFreemarkerEx::message, new FreeMarkerEngine(conf));
    }

    public static ModelAndView message(Request req, Response res) {

        Map<String, Object> params = new HashMap<>();
        params.put("name", req.params(":name"));
        return new ModelAndView(params, "hello.ftlh");
    }
}

我们为 FreeMarker 设置相同的应用程序。

Configuration conf = new Configuration(new Version(2, 3, 26));
conf.setClassForTemplateLoading(SparkFreemarkerEx.class, "/views");

我们使用 Configuration 类配置 FreeMarker。 模板文件将放置到 views 目录中,该目录必须位于类路径上。

get("/hello/:name/", SparkFreemarkerEx::message, new FreeMarkerEngine(conf));

FreeMarkerEngine 传递给 get 方法。

resources/views/hello.ftlh
<!DOCTYPE html>
<html>
    <head>
        <title>Home page</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>Hello ${name}</p>
    </body>
</html>

这是 hello.ftlh 模板文件;它引用了 ModelAndView 对象传递的 name 变量。

$ curl localhost:4567/hello/Lucy/
<!DOCTYPE html>
<html>
    <head>
        <title>Home page</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>Hello Lucy</p>
    </body>
</html>

Spring Boot FreeMarker

在下一个应用程序中,我们将 FreeMarker 集成到 Spring Boot Web 应用程序中。

build.gradle
src
└── main
    ├── java
    │   └── com
    │       └── zetcode
    │           ├── Application.java
    │           └── controller
    │               └── MyController.java
    └── resources
        └── templates
            ├── hello.ftlh
            └── index.ftlh

这是项目结构。

build.gradle
plugins {
    id 'org.springframework.boot' version '2.7.1'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.zetcode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-freemarker'
}

这是 Gradle 构建文件。 它包含 Spring Boot 和 FreeMarker 的依赖项。 无需在 Spring Boot 中配置 FreeMarker。 在 POM 文件中找到 FreeMarker 依赖项后,Spring Boot 会自动处理配置。

com/zetcode/Application.java
package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application  {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Application 设置 Spring Boot 应用程序。 @SpringBootApplication 注释将该类定义为配置类,启用自动配置,并启用组件扫描。

com/zetcode/controller/MyController.java
package com.zetcode.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MyController {

    @GetMapping("/")
    public String index(Model model) {
        return "index";
    }

    @GetMapping("/hello")
    public String hello(Model model, @RequestParam(value="msg", required=false,
            defaultValue="Freemarker") String msg) {

        model.addAttribute("message", msg);
        return "hello";
    }
}

这是 Spring Boot Web 应用程序的控制器类。 控制器有两个映射。 第一个映射解析为 index.ftl 文件,第二个映射解析为 hello.ftlh 文件。

resources/templates/index.ftlh
<!DOCTYPE html>
<html>
    <head>
        <title>Spring Boot Form</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <form action="/hello" method="get">
            <p>Message: <input type="text" name="msg"></p>
            <p>
                <input type="submit" value="Submit">
                <input type="reset" value="Reset">
            </p>
        </form>
    </body>
</html>

这是 index.ftlh 文件。 它有一个 HTML 表单,可将消息发送到服务器。

resources/templates/hello.ftlh
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Freemarker example</title>
</head>
<body>
    <p>${message}<p>
</body>
</html>

服务器以消息响应客户端。 响应由 hello.ftlh 模板文件创建。

$ ./gradlew bootRun

Spring Boot 启动一个嵌入式 Tomcat 服务器,在端口 8080 上侦听。

来源

Java FreeMarker 手册

在本教程中,我们使用了 FreeMarker 模板引擎。

作者

我叫 Jan Bodnar,是一位热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程