# FreeMarker - 教程

# 1. 概述

FreeMarker:

  • 一款模板引擎,一个 Java 类库
  • 一种基于 模板和要改变的数据,并用来生成文本的通用工具。
  • 文档: http://freemarker.foofun.cn/

技术选型对比:

技术 说明
Jsp Jsp 为 Servlet 专用,不能单独进行使用
Velocity Velocity 从 2010 年更新完 2.0 版本后,7 年没有更新。Spring Boot 官方在 1.4 版本后对此也不再支持
thmeleaf 新技术,功能较为强大,但是执行的效率比较低
freemarker 性能好,强大的模板语言、轻量

# 2. 快速开始

# 2.1. 环境搭建

依赖: (springboot 项目)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

配置:

server:
  port: 80
spring:
  application:
    # 应用名称
    name: test-freemarker
  freemarker:
    # 关闭模板缓存,方便调试
    cache: false
    # 模板文件的后缀名 (默认 .ftlh)
    suffix: .ftl
    settings:
      # 模板更新的延迟时间,为 0 则没有延迟
      template_update_delay: 0

注意:

  • freemarker 模板文件的扩展名通常是 ftl,也可以是 htmlxmljsp
  • 模板文件默认存放在 resources/templates

# 2.2. 入门案例

controller:

@Controller
public class FreeMarkerController {
    @GetMapping("/01-basic")
    public String basic(Model model) {
        model.addAttribute("name", "张三");
        model.addAttribute("stu", Student.of("李四", 18, new Date(), 123.3f));
        return "01-basic";
    }
}

bean:

@Data
@AllArgsConstructor(staticName = "of")
public class Student {
    private String name;
    private int age;
    private Date birthday;
    private Float money;
}

templates/01-basic.ftl:

<#-- 插值表达式 -->

<p>普通文本 String 展示:</p>
<ul>
  <li>name: ${ name }</li>
</ul>

<hr>

<p>Student 对象的属性展示: </p>
<ul>
  <li>stu.name : ${stu.name}</li>
  <li>stu.age : ${stu.age}</li>
</ul>

# 3. 指令语法

基础语法种类

集合指令(List 和 Map)

if 指令

运算符

空值处理

内建函数

# 3.1. 基础语法种类

注释:

<#-- 注释内容, 会被湖绿 -->

插值(表达式):

hello ${ name }

FTL 指令:

<#指令名称></#指令名称>

文本:

<#-- 模板中的普通文本,会直接输出 -->
文本内容

# 3.2. 集合指令

# 3.2.1. List

格式:

<#list 列表名 as 条目名>
  列表索引: ${ 条目名_index }
  元素的值: ${ 条目名 }
</#list>

示例:

@GetMapping("/02-list")
public String list(Model model) {
    List<Student> studentList = new ArrayList<>();
    studentList.add(Student.of("张三", 18, new Date(2000, 1, 2), 123.3f));
    studentList.add(Student.of("李四", 23, new Date(2002, 10, 28), 456.7f));

    model.addAttribute("studentList", studentList);

    return "02-list";
}
<table>
  <tr>
    <th>序号</th>
    <th>姓名</th>
    <th>年龄</th>
    <th>存款</th>
  </tr>
  <#list studentList as item>
    <tr>
      <th>${ item_index + 1 }</th>
      <th>${ item.name }</th>
      <th>${ item.age }</th>
      <th>${ item.money }</th>
    </tr>
  </#list>
</table>

# 3.2.2. Map

语法:

${MAP.属性名}

${MAP["属性名"]}

<#list MAP?keys as 键名变量>
  <p>
    索引: ${键名变量_index}  <br>
    属性: ${ MAP[键名变量].属性名 }
  </p>
</#list>

示例:

@GetMapping("/03-map")
public String map(Model model) {
    Map<String, Object> studentMap = new HashMap<>();

    studentMap.put("stu1", Student.of("王五", 28, new Date(2004, 1, 9), 166.7f));
    studentMap.put("stu2", Student.of("赵六", 32, new Date(2006, 2, 8), 887.7f));

    model.addAttribute("studentMap", studentMap);

    return "03-map";
}
<#-- 通过 .   map 中的属性 -->
<p>姓名: ${studentMap.stu1.name}</p>

<#-- 通过 []  map 中的属性 -->
<p>年龄: ${studentMap["stu1"].age}</p>

<#-- 遍历 map -->
<#list studentMap?keys as stuMapKey>
  <p>
    索引: ${ stuMapKey_index }  <br>
    姓名: ${ studentMap[stuMapKey].name }
  </p>
</#list>

# 3.3. if 指令

语法:

<#if condition>
  ...
<#elseif condition2>
  ...
<#elseif condition3>
  ...
<#else>
  ...
</#if>

示例:

<#--
  相等比较: 使用 “=” 或 “==” 都可以
  字符串字面量: 使用 单引号或双引号 界定
-->
<#if student.name = "张三">
    <span style="color: red">${ student.name }</span>
<#else>
    <span style="color: blue">${ student.name }</span>
</#if>

# 3.4. 运算符

算数运算符:

  • 加: +
  • 减: -
  • 乘: *
  • 除: /
  • 取模: %

比较运算符:

  • 相等: = 或者 ==
  • 不相等: !=
  • 大于: > 或者 gt
  • 大于或等于: >= 或者 gte
  • 小于: < 或者 lt
  • 小于或等于: <= 或者 lte

注意:

  • ==!= 可以用于 字符串、数字、日期 的比较
  • ==!= 两边必须是相同类型,否则会报错
  • 字符串比较是大小写敏感的
  • 使用 gt 替代 >,FreeMarker 会将 > 解析成标签的结束字符
    • 可以使用小括号包裹: <#if (2 > 1)>

逻辑运算符:

  • 与: &&
  • 或: ||
  • 非: !

# 3.5. 空值处理

判断 变量 是否存在:

<#-- wahh 变量不存在 -->
<#if wahh??>
    wahh 变量 存在
<#else>
    wahh 变量 不存在
</#if>

<br>

<#-- user 变量  null -->
<#if user??>
    user 变量 不为 null
<#else>
    user 变量 为 null
</#if>

默认值:

<#-- 不存在的变量 -->
<p> ${ wahh ! "wahh 不存在!!!" } </p>

<#--  null 的变量 -->
<p> ${ user ! "user 为 null!!!" } </p>

<#-- 嵌套 取值 -->
<p> ${ (user.address.city) ! "user.address.city 更为 null !!!" } </p>

# 3.6. 内建函数

语法:

变量名?函数名称

# 3.6.1. 集合的大小

使用:

  ${ 集合名?size }

示例:

  <p> 集合的大小: ${ studentList ? size } </p>

# 3.6.2. 日期格式化

<#--
  model.addAttribute("today", new Date());
-->

<#-- Jan 9, 2026-->
<p>date: ${ today ? date }</p>

<#-- 7:26:38 PM-->
<p>time: ${ today ? time }</p>

<#-- Jan 9, 2026 7:26:38 PM-->
<p>datetime: ${ today ? datetime }</p>

<#-- 2026年01月09日 19时26分38秒-->
<p>string("yyyy年MM月dd日 HH时mm分ss秒"): ${ today ? string("yyyy年MM月dd日 HH时mm分ss秒") }</p>

# 3.6.3. 禁止数字格式化

<#--
  model.addAttribute("num", 123456789L);
-->

<#-- 123,456,789 -->
<p>${ 123456789 }</p>

<#-- 123,456,789 -->
<p>${ num }</p>

<#-- 123456789 -->
<p>${ num ? c }</p>

# 3.6.4. JSON字符串转对象

<#-- 定义一个字符串类型的 变量 -->
<#assign userStr="{ 'name': '张三' }" />

<#--  userStr 转换为对象 -->
<#assign userObj=userStr ? eval />

<p> userObj.name : ${ userObj.name } </p>

# 4. 输出解析后的文本

配置:

spring:
  freemarker:
    # 指定模板所在的目录
    template-loader-path: classpath:/templates/ # 默认

使用:

@SpringBootTest
class FreeMarkerDemoApplicationTests {
    @Autowired
    private Configuration configuration;

    @Test
    void quickstart() throws Exception  {
        // 数据
        Map<String, Object> data = new HashMap<>();
        data.put("msg", "娃哈哈");

        // 模板
        Template template = configuration.getTemplate("06-static-output.ftl");
        // src/main/resources/templates/06-static-output.ftl
        // <p>静态化输出: ${ msg }</p>

        // 输出
        StringWriter out = new StringWriter();

        // 处理
        template.process(data, out);

        String content = out.toString();

        System.out.println(content);
        //=> <p>静态化输出: 娃哈哈</p>
    }
}

# 5. 参考

本章目录