介绍
Freemarker 是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。
Freemarker 是免费的,基于 Apache 许可证 2.0 版本发布。其模板语言为 FreeMarker Template Language(FTL),属于简单、专用的语言。需要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,主要用于如何展现数据, 而在模板之外注意于要展示什么数据。
常用的 Java 模板引擎还有哪些?
Jsp、Freemarker、Thymeleaf 、Velocity 等。
模板+数据模型=输出
Freemarker 并不关心数据的来源,只是根据模板的内容,将数据模型在模板中显示并输出文件(通常为 html,也可以生成其它格式的文本文件)。
快速入门
默认情况下 SpringMVC 支持 Freemarker 视图格式,这里就创建 Spring Boot+Freemarker 工程用于测试。
创建测试工程
创建一个 Spring Boot 的测试工程专门用于 Freemarker 的功能测试,添加依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐freemarke</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
配置文件
配置 application.yml
和 logback-spring.xml
,从 cms 工程拷贝这两个文件,进行更改,logback-spring.xml
无需更改,application.yml
内容如下:
server:
port: 8088
spring:
application:
name: freemarker-test
freemarker:
# 设置模板后缀名
suffix: .ftl
# 设置文档类型
content-type: text/html
# 设置页面编码格式
charset: UTF-8
# 关闭缓存,及时刷新,上线生产环境需要修改为true
cache: false
# 设定 ftl 文件路径
template-loader-path: classpath:/templates
创建模型类
创建模型类用于测试:
package xyz.zze.study.freemarker.bean;
import lombok.Data;
import lombok.ToString;
import java.util.Date;
import java.util.List;
@Data
@ToString
public class Student {
private String name; // 姓名
private Integer age; // 年龄
private Date birthday; // 生日
private Float money; // 钱包
private List<Student> friends; // 好友列表
private Student bestFriend; // 最好的朋友
}
创建模板
在 src/main/resources
下创建 templates
目录,此目录为 freemarker 的默认模板存放目录。 在 templates
下创建模板文件 test.ftl
,模板中的 ${name}
终会被 freemarker 替换成具体的数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Freemarker</title>
</head>
<body>
Hello ${name}
</body>
</html>
创建控制器
创建 Controller 类,向 Map 中添加 name
后返回模板文件。
package xyz.zze.study.freemarker.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
@RequestMapping("/freemarker")
@Controller
public class TestController {
@GetMapping("/test1")
public String freemarker(Map<String, Object> map) {
map.put("name", "zze");
// 返回 src/resources/templates 下的模板名称
return "test";
}
}
测试
请求 localhost:8088/freemarker/test1
,响应如下:
Freemarker使用
核心指令
在学习指令前需先了解 Freemarker 的如下知识点:
- 注释:即
<#-- -->
,介于其之间的内容会被 Freemarker 忽略; - 插值(Interpolation):即使用
${ }
来引用对象,Freemarker 会用对象真实的值代替${ }
; - FTL 指令:与 OHTML 标记类似,变量名前加
#
予以区分,Freemarker 会解析标签中的表达式或逻辑; - 文本:仅文本信息,直接输出内容。
修改控制器,往 map 中填充数据用于测试:
package xyz.zze.study.freemarker.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import xyz.zze.study.freemarker.bean.Student;
import java.util.*;
@RequestMapping("/freemarker")
@Controller
public class TestController {
@GetMapping("/test1")
public String freemarker(Map<String, Object> map) {
// 向数据模型放数据
map.put("name", "zze");
Student stu1 = new Student();
stu1.setName("小明");
stu1.setAge(18);
stu1.setMoney(1000.86f);
stu1.setBirthday(new Date());
Student stu2 = new Student();
stu2.setName("小红");
stu2.setMoney(200.1f);
stu2.setAge(19);
stu2.setBirthday(new Date());
List<Student> friends = new ArrayList<>();
friends.add(stu1);
stu2.setFriends(friends);
stu2.setBestFriend(stu1);
List<Student> stus = new ArrayList<>();
stus.add(stu1);
stus.add(stu2);
//向数据模型放数据
map.put("stus", stus);
//准备map数据
Map<String, Student> stuMap = new HashMap<>();
stuMap.put("stu1", stu1);
stuMap.put("stu2", stu2);
map.put("stu1", stu1);
map.put("stuMap", stuMap);
// 返回 src/resources/templates 下的模板名称
return "test";
}
}
list 指令
【测试遍历 list】 修改 test.ftl
的 <body>
中的内容为如下:
<table border="1">
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list stus as stu>
<tr>
<td>${stu_index + 1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#list>
</table>
输出:
_index
:得到循环记录的下标,使用方法是在变量后边加_index
,它的值是从0
开始。
【测试遍历 map】修改 test.ftl
的 <body>
中的内容为如下:
输出 stu1 的学生信息: <br/>
姓名: ${stuMap['stu1'].name}<br/>
年龄: ${stuMap['stu1'].age}<br/>
输出 stu2 的学生信息: <br/>
姓名: ${stuMap.stu2.name}<br/>
年龄: ${stuMap.stu2.age}<br/>
遍历输出两个学生信息: <br/>
<table border="1">
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list stuMap?keys as k>
<tr>
<td>${k_index + 1}</td>
<td>${stuMap[k].name}</td>
<td>${stuMap[k].age}</td>
<td>${stuMap[k].money}</td>
</tr>
</#list>
</table>
输出:
if 指令
if
指令即判断指令,是常用的 FTL 指令,Freemarker 在解析时遇到 if
会进行判断,条件为真则输出 if
后面代码块中间的内容,否则跳过内容不再输出。
修改 test.ftl
的 <body>
中的内容为如下:
<table border="1">
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list stus as stu>
<tr>
<td <#if stu_index%2==0>style="background: red" </#if>>${stu_index + 1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#list>
</table>
输出:
实现的功能是:如果遍历的记录索引是偶数,就给序号单元格背景色设为红色。
运算符
算数运算符
FreeMarker 表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+
、-
、*
、/
、%
。
逻辑运算符
逻辑运算符有如下几个:
- 逻辑与:
&&
; - 逻辑或:
||
; - 逻辑非:
!
;
逻辑运算符只能作用于布尔值,否则将产生错误
比较运算符
表达式中支持的比较运算符有如下几个:
=
或者==
:判断两个值是否相等;!=
:判断两个值是否不等;>
:或者gt
:判断左边值是否大于右边值;>=
:或者gte
:判断左边值是否大于等于右边值;<
或者lt
:判断左边值是否小于右边值;<=
或者lte
:判断左边值是否小于等于右边值;’
注意:
=
和!=
可以用于字符串、数值和日期来比较是否相等,但=
和!=
两边必须是相同类型的值,否则会产生错误,而且 FreeMarker 是精确比较,x
、X
是不等的。其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用gt
等字母运算符代替>
会有更好的效果,因为 FreeMarker 会把>
解释成 FTL 标签的结束字符,当然,也可以使用括 号来避免这种情况,如:<#if (x>y)>
。
空值处理
判断某变量是否存在使用 ??
,语法为:变量??
,如果该变量存在,返回 true
,否则返回 false
;
例:为当值 stus
为空报错可以加上判断如下:
<#if stus??>
...
</#if>
还可给缺失的变量给定默认值,语法为 变量!默认值
,当变量为空时则显示默认值。
例:${name!""}
表示如果 name
为空则显示空字符串。
如果是嵌套的对象建议使用 ()
括起来。
例:${(stu.bestFriend.name)!""}
表示如果 stu
或 bestFriend
或 name
为空,则默认显示空字符串。
内建函数
Freemarker 的内建函数语法格式为 变量?函数名称
,常用内建函数如下:
获得集合的大小
集合的大小在 Freemarker 中是可以通过内置函数 size
获取,如下:
${集合变量?size}
日期格式化
- 显示年月日:
${today?date}
; - 显示时分秒:
${today?time}
; - 显示日期+时间:
${today?datetime}
; - 自定义格式化:
${today?string("yyyy-MM-dd")}
;
这里的
today
代指日期类型的一个变量。
转字符串
字符串的转换需要使用到内置函数 c
。
例:假如在模板中能取到一个数值类型的变量 num
,它的值为 123123113
,那么直接使用 ${num}
输出的结果将会是 123,123,113
,即每三位使用逗号进行分隔,如果不想显示为每三位分隔的数字,可以使用 c 函数将数字转换为字符串输出,即 $num?c
。
json 串转对象
还可将 json 字符串转换为对象使用,先看如下示例:
<!-- 模板 -->
<#assign text="{'name':'张三','age':23}" />
<#assign data=text?eval />
${data.name} ${data.age}
对应输出结果为:
张三 23
assgin
的作用是定义一个变量,而 eval
函数的作用就是将 json 字符串转换为一个对象。
静态化
测试资源目录(src/test/resources
)下准备模板文件 templates/test.tpl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Freemarker</title>
</head>
<body>
Hello ${name}
</body>
</html>
创建测试方法如下:
package xyz.zze.study.freemarker;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.jupiter.api.Test;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class Test01 {
//基于模板文件静态化
@Test
public void testGenerateHtmlByTpl() throws IOException, TemplateException {
//创建配置类
Configuration configuration = new Configuration(Configuration.getVersion());
//设置模板路径
String classpath = this.getClass().getResource("/").getPath();
configuration.setDirectoryForTemplateLoading(new File(classpath + "/templates/"));
//设置字符集
configuration.setDefaultEncoding("utf-8");
//加载模板
Template template = configuration.getTemplate("test.ftl");
//数据模型
Map<String, Object> map = new HashMap<>();
map.put("name", "zze");
//静态化
String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
//静态化内容
System.out.println(content);
}
//基于模板字符串静态化
@Test
public void testGenerateHtmlByString() throws IOException, TemplateException {
//创建配置类
Configuration configuration = new Configuration(Configuration.getVersion());
//模板字符串
String tplStr = "<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>Hello Freemarker</title>\n" +
"</head>\n" +
"<body>\n" +
"Hello ${name}\n" +
"</body>\n" +
"</html>";
//设置模板加载器
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate("hello_tpl", tplStr);
configuration.setTemplateLoader(stringTemplateLoader);
//得到模板
Template helloTpl = configuration.getTemplate("hello_tpl","utf-8");
//数据模型
Map<String, Object> map = new HashMap<>();
map.put("name", "zze");
//静态化
String content = FreeMarkerTemplateUtils.processTemplateIntoString(helloTpl, map);
//静态化内容
System.out.println(content);
}
}
上述两个测试方法的输出结果如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Freemarker</title>
</head>
<body>
Hello zze
</body>
</html>
将最后输出的字符串保存到
html
文件即完成了静态化。
评论区