Spring Boot对Freemarker的整合非常直接,引入对应starter后,自动配置模块会完成视图解析器的初始化,开发者只需要关注模板目录和Controller返回的视图名称。Freemarker模板文件使用类似HTML的语法,通过指令和插值表达式完成数据渲染,在后端管理页面、邮件正文生成等场景仍然非常实用。本文以用户列表页为例,从零跑通整个渲染流程,并说明几个容易踩坑的配置项。

一、添加依赖与配置模板参数
在Spring Boot项目中整合Freemarker,最方便的方式是使用官方提供的starter。Maven工程在pom.xml中加入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
如果使用Gradle,则在build.gradle中增加implementation 'org.springframework.boot:spring-boot-starter-freemarker'。依赖引入后,Spring Boot的FreemarkerAutoConfiguration会自动创建FreeMarkerConfigurer和FreeMarkerViewResolver,默认从classpath:/templates/目录加载模板,默认后缀是.ftlh。注意早期版本的Spring Boot默认后缀是.ftl,升级项目时最好显式指定后缀,避免模板找不到。
在application.properties或application.yml中,通常只需要配置少量参数。例如开发阶段关闭缓存,保证修改模板后刷新页面立即生效;同时统一字符集,避免中文乱码:
spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.content-type=text/html;charset=UTF-8 spring.freemarker.suffix=.ftl spring.freemarker.template-loader-path=classpath:/templates/
这里显式把后缀设置为.ftl,如果你更喜欢Spring Boot 2之后的默认后缀.ftlh,直接使用即可,但Controller返回值要保持一致。template-loader-path也可以配置为其他目录,比如classpath:/views/,但生产环境一般保持默认目录结构更清晰。
二、Controller传递数据与模板文件编写
动态页面离不开后端数据。Freemarker本身不关心数据来自数据库还是远程接口,它只负责把模型中的数据渲染到模板中。下面的Controller返回一个视图名,同时把用户列表放进Model:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/list")
public String listUsers(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
model.addAttribute("title", "用户列表");
return "user/list";
}
}
这里返回的字符串user/list会交给视图解析器,最终定位到src/main/resources/templates/user/list.ftl。如果模板放在子目录,注意返回值不要带后缀,也不要以斜杠开头。
模板文件本质上是一个HTML文件,只是嵌入了Freemarker的插值和指令。下面是一个简单的列表页,遍历users集合并输出表格:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
<table border="1" cellpadding="8">
<tr>
<th>ID</th>
<th>姓名</th>
<th>注册时间</th>
</tr>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.createdAt?string("yyyy-MM-dd HH:mm")}</td>
</tr>
</#list>
</table>
</body>
</html>
在模板中,${...}表示插值表达式,Freemarker会读取User对象的id和name属性。日期类型默认输出可能不符合中文习惯,所以使用内置函数?string指定格式。<#list>指令负责遍历,需要用</#list>闭合。模板语法与HTML标签混写时,建议保持缩进一致,便于维护。
三、核心指令与空值处理
除了遍历,Freemarker还支持条件判断、变量定义、宏和包含。条件判断使用<#if>,可以结合??判断变量是否存在。比如当用户列表为空时显示提示:
<#if users?? && users?size > 0>
<table border="1">
<tr><th>姓名</th></tr>
<#list users as user>
<tr><td>${user.name}</td></tr>
</#list>
</table>
<#else>
<p>暂无用户数据</p>
</#if>
这里users??先判断users是否存在于模型,users?size > 0表示集合大小大于0。注意模板中的大于号需要写成>,否则会破坏指令解析。也可以使用users?has_content一次性判断非空。
空值处理是Freemarker项目中经常遇到的问题。如果模型里缺少某个属性,直接用${user.address.city}可能会抛出InvalidReferenceException。推荐在不确定的取值处添加默认值,语法是${user.address.city!"未知城市"}。如果只想在null时不输出任何内容,可以写成${user.phone!}。对于嵌套对象,还可以使用${(user.address.city)!}进行整体安全访问。
日期和数字格式化也很常用。日期格式可以通过?string指定,数字保留小数位使用?string("0.00"),金额场景尤其常见。如果需要调用Java静态方法,Freemarker不直接支持,建议在Controller中预处理数据,把格式化结果放入模型,避免模板逻辑过于复杂。
四、抽取公共布局与优化建议
后台管理系统的页面通常有统一的顶部导航和侧边栏,如果每个模板都复制一份HTML结构,后期修改会非常麻烦。Freemarker支持<#include>指令,可以把公共部分拆成独立文件。例如创建common/header.ftl和common/footer.ftl,在业务模板中使用:
<#include "common/header.ftl">
<main>
<h1>${title}</h1>
<p>页面主体内容</p>
</main>
<#include "common/footer.ftl">
如果公共区域需要传递参数,可以使用<#macro>定义宏,像函数一样复用。宏可以放在单独的.ftl文件中,通过<#import>引入,调用时传入页面标题、激活菜单等参数,比简单include更灵活。不过宏会让模板逻辑增加,适合布局比较稳定的项目。
开发过程中,模板缓存建议关闭,修改后直接刷新浏览器查看。生产环境一定要开启缓存,减少每次请求的模板解析开销。中文乱码问题通常源于模板文件本身不是UTF-8编码,或者响应头content-type没有指定charset。确保application.properties中配置了spring.freemarker.charset=UTF-8,同时IDE里模板文件保存为UTF-8无BOM格式,基本可以避免乱码。
与Thymeleaf相比,Freemarker语法更接近JSP时代的EL表达式,学习成本低,而且模板文件可以被非Java系统复用,比如Python或Node.js的Freemarker实现。如果你的项目需要生成离线HTML报告或邮件正文,Freemarker是轻量的选择;如果页面与前端框架交互频繁,建议只把Freemarker用于初始页面容器,数据通过接口异步加载。
最后,所有模板文件的路径和Controller返回值要严格对应。遇到404或找不到模板错误时,先检查返回的视图名是否带后缀、模板是否放在classpath:/templates/目录下、spring.freemarker.suffix配置是否与文件后缀一致。把这些基础项排查清楚,整合流程通常不会出现复杂问题。
Spring BootFreemarker模板引擎修改时间:2026-09-23 05:13:02