Spring Boot 整合 Freemarker 模板引擎

在前后端分离架构大行其道的今天,模板引擎依然有着重要的地位和不可代替性。

Freemarker 是一款界开源的老牌模板引擎,使用 Java 开发,Spring 官方对 Freemarker 提供了支持。本文将会带你学习如何在 Spring Boot 中整合 Freemarker。

创建项目

pom.xml 添加 spring-boot-starter-webspring-boot-starter-freemarker 依赖。

<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>

编写模板

根据 Spring Boot 约定,模板文件应该放在 src/main/resources 目录下的 templates 目录中。

templates 目录中,创建 index 文件夹,用于存放渲染主页的 index.ftl 模板视图,如下:

Freemarker 模板引擎目录

其中 index.ftl 内容如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Freemarker</title>
    </head>
    <body>
        Hello ${title}!
    </body>
</html>

如上,模板只是简单地输出了 Model 中的 title 属性。

配置属性

Spring Boot 提供了很多配置属性,可用于在 application.yaml | properties 中定制 Freemarker。 这些属性都以 spring.freemarker 开头。

常用配置如下:

spring:
  freemarker:
    # 启用 freemarker 模板
    enabled: true
    # 是否缓存
    cache: false
    # Content Type
    content-type: text/html
    # 编码
    charset: utf-8
    # 模板后缀
    suffix: .ftl
    # 引用 request 的属性名称
    request-context-attribute: request
    # 是否暴露 request 域中的属性
    expose-request-attributes: false
    # 是否暴露session域中的属性
    expose-session-attributes: false
    # request 域中的属性是否可以覆盖 controller 的 model 的同名项。默认 false,如果发生同名属性覆盖的情况会抛出异常
    allow-request-override: true
    # session 域中的属性是否可以覆盖 controller 的 model 的同名项。默认 false,如果发生同名属性覆盖的情况会抛出异常
    allow-session-override: true
    # 暴露官方提供的宏
    expose-spring-macro-helpers: true
    # 启动时检查模板位置是否有效
    check-template-location: true
    # 优先加载文件系统的模板
    prefer-file-system-access: true
    # 模板所在位置(目录)
    template-loader-path:
      - classpath:/templates/
    settings:
      datetime_format: yyyy-MM-dd HH:mm:ss      # date 输出格式化
      template_update_delay: 30m                # 模板引擎刷新时间
      default_encoding: utf-8                   # 默认编码

完整的配置信息,可参阅官方文档

spring.freemarker.expose-spring-macro-helpers 设置为 true 可以暴露 spring 官方提供的宏。这些宏提供了国际化、表单绑定等等功能。

这些宏定义在 spring-webmvc 模块的 /org/springframework/web/servlet/view/freemarker/spring.ftl 文件中。你可以在模板中使用 #import 指令导入、使用,如下:

<#import "/spring.ftl" as spring/>

“/spring.ftl” 必须以 “/” 开头,否则会异常:

Caused by: freemarker.template.TemplateNotFoundException: Template not found for name "index/spring.ftl".
The name was interpreted by this TemplateLoader: MultiTemplateLoader(loader1 = FileTemplateLoader(baseDir="C:\eclipse\eclipse-jee-2023-09-R-win32-x86_64\app\springdoc-demo\target\classes\templates", canonicalBasePath="C:\eclipse\eclipse-jee-2023-09-R-win32-x86_64\app\springdoc-demo\target\classes\templates\"), loader2 = ClassTemplateLoader(resourceLoaderClass=org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer, basePackagePath="" /* relatively to resourceLoaderClass pkg */)).
   at freemarker.template.Configuration.getTemplate(Configuration.java:2957) ~[freemarker-2.3.32.jar:2.3.32]
   at freemarker.core.Environment.getTemplateForInclusion(Environment.java:3062) ~[freemarker-2.3.32.jar:2.3.32]
   at freemarker.core.Environment.getTemplateForInclusion(Environment.java:3024) ~[freemarker-2.3.32.jar:2.3.32]
   at freemarker.core.Environment.getTemplateForImporting(Environment.java:3186) ~[freemarker-2.3.32.jar:2.3.32]
   at freemarker.core.Environment.importLib(Environment.java:3171) ~[freemarker-2.3.32.jar:2.3.32]
   at freemarker.core.Environment.importLib(Environment.java:3135) ~[freemarker-2.3.32.jar:2.3.32]
   at freemarker.core.LibraryLoad.accept(LibraryLoad.java:65) ~[freemarker-2.3.32.jar:2.3.32]
   ... 55 common frames omitted

更详细的用法你可以参考 官方文档

spring.freemarker.template-loader-path 是一个数组,支持定义多个模板目录。不仅支持 classpath 目录,也支持本地磁盘目录,如:file:${user.dir}/templates/

定义 Controller

创建 IndexController,返回 ModelAndView,渲染主页模板视图:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@RequestMapping("/")
@Controller
public class IndexController {

    @GetMapping
    public ModelAndView index () {
        ModelAndView modelAndView = new ModelAndView("index/index");
        // 添加 title 属性到 Model
        modelAndView.addObject("title", "Freemarker");
        return modelAndView;
    }
}

ModelAndView 对象的构造函数中指定要渲染的视图(模板),不需要添加 .ftl 后缀,因为已经统一在配置文件中定义了。

启动应用,使用 curl (浏览器也可以)访问 localhost:8080,响应如下:

$ curl localhost:8080
<!DOCTYPE html>
<html>
        <head>
                <meta charset="UTF-8">
                <title>Freemarker</title>
        </head>
        <body>
                Hello Freemarker!
        </body>
</html>

如果所见,成功地渲染了 index.ftl,输出了 Model 中的 title 属性。

Configuration

有时候,你可能需要对模板进行深层次的定制,如:设置自定义函数、定义全局变量。

那么你可以在任意 Bean 中通过 IOC 注入:freemarker.template.Configuration 对象,进行自定义设置。


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import freemarker.template.TemplateModelException;
import jakarta.annotation.PostConstruct;

@Configuration
public class FreemarkerConfiguration {

    // 注入 Configuration
    @Autowired
    private freemarker.template.Configuration configuration;

    @PostConstruct
    public void configuration() throws TemplateModelException {
        // 添加全局变量
        this.configuration.setSharedVariable("app", "Spring Boot");
    }
}