Skip to content

freemarker

logo

简介

Freemarker是一款模板引擎,它的发展历史可以追溯到2002年。当时,JavaServer Pages(JSP)是Java Web开发中最流行的技术之一,但是JSP的语法和标签库使得模板难以维护和重用。于是,Freemarker的创始人Daniel Dekany开始开发一款新的模板引擎,它的目标是提供一种简单、灵活、易于维护和重用的模板语言。

Freemarker的第一个版本于2003年发布,它的语法和JSP完全不同,但是它提供了类似于JSP的标签库和表达式语言。随着时间的推移,Freemarker不断发展壮大,增加了更多的功能和特性,例如自定义指令、宏、条件语句等等。Freemarker也逐渐成为Java Web开发中最受欢迎的模板引擎之一,被广泛应用于各种Web框架和应用程序中。

Freemarker具有以下特点:

  • Freemarker适合被设计用来生成HTML Web页面,特别是基于MVC模式的应用程序。

  • 虽然Freemarker 具有一些编程的能力,但通常由Java程序准备要显示的数据,由Freemarker 生成页面,通过模板显示准备的数据 。

  • Freemarker 不是一个Web应用框架,而适合作为Web应用框架一个组件。

  • Freemarker 与容器无关,因为它并不知道HTTP或Servlet。

  • Freemarker 更适合作为Model2框架(如Struts)的视图组件,你也可以在模板中使用JSP标记库。


原理

数据模型+模版=输出

Freemarker工作原理


Spring Boot集成

安装

pom.xml 添加以下依赖:

1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

开发流程

下面是使用spring-boot-starter-freemarker的流程:

在pom.xml文件中添加spring-boot-starter-freemarker依赖:

1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

application.propertiesapplication.yml文件中配置Freemarker相关属性:

1
2
3
4
5
# Freemarker配置
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.cache=false

创建Freemarker模板文件,例如index.ftl:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
    <title>Spring Boot Freemarker Example</title>
</head>
<body>
    <h1>Hello, {name}!</h1>
</body>
</html>

创建Controller类,例如IndexController.java,返回模板名称:

1
2
3
4
5
6
7
8
@Controller
public class IndexController {
    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("name", "World");
        return "index";
    }
}

配置

关于spring-boot-starter-freemarker的配置参数,常见的有以下几个:

参数名 描述 默认值
spring.freemarker.allow-request-override 是否允许覆盖请求属性 true
spring.freemarker.cache 是否启用模板缓存 true
spring.freemarker.charset 模板文件编码 UTF-8
spring.freemarker.check-template-location 是否检查模板文件位置 true
spring.freemarker.content-type 模板文件的Content-Type text/html
spring.freemarker.expose-request-attributes 是否将请求属性暴露给模板 false
spring.freemarker.expose-session-attributes 是否将会话属性暴露给模板 false
spring.freemarker.expose-spring-macro-helpers 是否将Spring宏助手暴露给模板 true
spring.freemarker.prefer-file-system-access 是否优先使用文件系统访问模板 true
spring.freemarker.suffix 模板文件后缀 .ftl

Hello World!

在STS中创建新工程,选择Spring Starter Project向导

Spring Starter Project

输入工程基本信息:

  • Service URL: https://start.aliyun.com
  • Name: HelloFreemarker

Project info

为工程选择Spring Web依赖,支持Spring及Spring MVC

Dependences

工程创建向导完成后,Mavan配置文件pom.xml的关键依赖如下:

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

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>
源代码
HelloFreemarker/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.0</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>HelloFreemarker</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>HelloFreemarker</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>17</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

com.example.demo下创建类Hi1Controller

// http://localhost:8080/hello1
@RestController
public class Hi1Controller{
  @GetMapping("/hello1")
  public ModelAndView hi1(){
    ModelAndView modelAndView = new ModelAndView("hi1");
    modelAndView.addObject("message", "Hello, Freemarker!");

    return modelAndView;
  }
}
源代码
HelloFreemarker/src/main/java/com/example/demo/Hi1Controller.java
package com.example.demo;

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

@RestController
public class Hi1Controller{
  @GetMapping("/hello1")
  public ModelAndView hi1(){
    ModelAndView modelAndView = new ModelAndView("hi1");
    modelAndView.addObject("message", "Hello, Freemarker!");

    return modelAndView;
  }
}

src/main/resources/templates下创建类hi1.ftl

<h1>${message}</h1>
源代码
<!DOCTYPE html>
<html>

<head>
  <title>首页</title>
  <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
  <h1>${message}</h1>
</body>

</html>

com.example.demo下创建类Hi2Controller

// http://localhost:8080/hello2
@Controller
public class Hi2Controller{
  @RequestMapping("/hello2")
  public String hi2(Model model){
    ArrayList<String> messages = new ArrayList<String>();
    messages.add("Hello 1!");
    messages.add("Hello 2!");
    messages.add("Hello 3!");
    model.addAttribute("messages", messages);

    return "hi2";
  }
}
源代码
HelloFreemarker/src/main/java/com/example/demo/Hi1Controller.java
package com.example.demo;

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

@RestController
public class Hi1Controller{
  @GetMapping("/hello1")
  public ModelAndView hi1(){
    ModelAndView modelAndView = new ModelAndView("hi1");
    modelAndView.addObject("message", "Hello, Freemarker!");

    return modelAndView;
  }
}

src/main/resources/templates下创建类hi2.ftl

2
3
4
<#list messages as message>
<h1>${message}</h1>
</#list>
源代码
<!DOCTYPE html>
<html>

<head>
  <title>首页</title>
  <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
  <#list messages as message>
  <h1>${message}</h1>
  </#list>
</body>

</html>

向导完成后生成的目录结构如下:

HelloFreemarker:.
  HELP.md
  mvnw
  mvnw.cmd
  pom.xml  // Maven配置文件
└─src
    ├─main
      ├─java
        └─com
            └─example
                └─demo
                        Hi1Controller.java //Hi1Controller
                        Hi2Controller.java //Hi2Controller
                        HelloFreemarkerApplication.java //主程序入口
            └─resources
            application.properties  //配置文件
          ├─static
          └─templates
    └─test
        └─java
            └─com
                └─example
                    └─demo
                            HelloFreemarkerApplicationTests.java  

Boot Dashboard中启动HelloFreemarker

在浏览器访问http://localhost:8080/hello1,结果如下:

在浏览器访问http://localhost:8080/hello2,结果如下:


基本语法

FreeMarker标记

FreeMarker会在输出时用实际值进行替代

例如:

${stockNum?default('')}

标准的FTL标记。大部分FreeMarker指令都以#开始,可以明显地与html标记区分

<#if user = "BigJoe">our beloved leader</#if>

同时,FTL文件的注释包含在<#--和-->(而不是)之间

@作为用户定义指令使用宏变量时,使用@替代FTL标记中的#。后面将详细介绍宏


标准的FTL标记指令

if, else, elseif

switch, case, default, break

list, break

include

Import

assign

macro, nested, return


if, else, elseif指令

1
2
3
4
5
6
7
8
9
<#if condition>
  ...
<#elseif condition2>
  ...
<#elseif condition3>
    ......
<#else>
    ...
</#if>
1
2
3
4
5
6
7
8
<#if x = 1> 
  x is 1 
</#if>
<#if x = 1> 
    x is 1
<#else> 
    x is not 1 
</#if>

switch, case, default, break指令

<#switch being.size>
  <#case "small"> 
    This will be processed if it is small 
    <#break> 
  <#case "medium"> 
    This will be processed if it is medium  
    <#break> 
  <#case "large">
    This will be processed if it is large
    <#break>
  <#default>
    This will be processed if it is neither
</#switch>

list, break指令

1
2
3
4
<#list sequence as item>
  ...
<#if item="spring"><#break></#if>...
</#list>

关键字item_index:是list当前值的下标item_has_next:判断list是否还有值

1
2
3
4
<#assign seq = ["winter", "spring", "summer", "autumn"]>
<#list seq as x> 
${x_index + 1}. ${x}<#if x_has_next>,</#if>
</#list>

输出 1. winter, 2. spring, 3. summer, 4. autumn 


include指令

<#include filename>
or<#include filename options>

/common/copyright.ftl包含内容

Copyright 2001-2002 ${me}<br>All rights reserved.  

模板文件

1
2
3
<#assign me = "Juila Smith">
  <h1>Some test</h1><p>Yeah.<hr>
<#include "/common/copyright.ftl"  encoding="utf-8"> 

import指令

<#import path as hash>

类似于java里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件

假设mylib.ftl里定义了宏copyright那么我们在其他模板页面里可以这样使用

<#import "/libs/mylib.ftl" as my>
<@my.copyright date="1999-2002"/>

"my"在freemarker里被称作namespace


assign指令

生成变量,并且给变量赋值 常用的语法形式:

1
2
3
4
5
6
7
8
9
<#assign name=value>
Or
<#assign name1=value1 name2=value2 ...nameN=valueN>
Or
<#assign name> capture this</#assign>
Or
<#assign name in namespacehash>
    capture this
</#assign> 

macro, nested, return指令

1
2
3
4
5
6
7
<#macro name param1 param2 ...paramN>
      ...
  <#nested loopvar1, loopvar2, ..., loopvarN>
      ...
    <#return>
      ...
</#macro> 
1
2
3
4
5
6
7
<#macro test foo bar="Bar“  baaz=-1>  
  Test text, and the params: ${foo}, ${bar},${baaz}
</#macro>
<@test foo="a" bar="b" baaz=5*5-2/>
<@test foo="a" bar="b"/>
<@test foo="a" baaz=5*5-2/>
<@test foo="a"/> 

输出

1
2
3
4
Test text, and the params: a, b, 23 
Test text, and the params: a, b, -1  
Test text, and the params: a, Bar, 23   
Test text, and the params: a, Bar, -1

内建函数

Freemarker提供了许多内置函数来处理模板中的数据。这些内置函数可以分为以下几类:

  1. 字符串处理函数:这些函数用于处理字符串,例如截取、替换、转换大小写等。常用的函数包括substring、replace、capitalize等。

  2. 数字处理函数:这些函数用于处理数字,例如四舍五入、格式化等。常用的函数包括round、number、currency等。

  3. 集合处理函数:这些函数用于处理集合,例如排序、过滤、查找等。常用的函数包括sort、filter、contains等。

  4. 时间处理函数:这些函数用于处理时间,例如格式化、计算时间差等。常用的函数包括date、time、datetime等。

  5. 条件判断函数:这些函数用于进行条件判断,例如判断是否为空、是否相等等。常用的函数包括if、exists、equals等。

  6. 其他函数:这些函数包括一些其他的常用函数,例如输出、循环、引入模板等。常用的函数包括print、list、include等。

使用这些内置函数的语法非常简单,只需要在模板中使用函数名和参数即可。例如,使用cap_first字符串中的首单词的首字母大写语法如下:

1
2
3
${"  green mouse"?cap_first}
${"GreEN mouse"?cap_first}
${"- green mouse"?cap_first}