1、总览
官网:http://freemarker.foofun.cn/
视频地址:https://www.bilibili.com/video/BV1zZ4y1u7iA
2、FreeMarker概述
2.1 FreeMarker概念
FreeMarker 是⼀款 模板引擎: 即⼀种基于模板和要改变的数据, 并⽤来⽣成输出⽂本(HTML⽹⻚,电⼦邮件,配置⽂件,源代码等)的通⽤⼯具。 是⼀个Java类库。
FreeMarker 被设计⽤来⽣成 HTML Web ⻚⾯,特别是基于 MVC 模式的应⽤程序,将视图从业务逻辑中抽离处理,业务中不再包括视图的展示,⽽是将视图交给 FreeMarker 来输出。虽然 FreeMarker 具有⼀些编程的能⼒,但通常由 Java 程序准备要显示的数据,由 FreeMarker ⽣成⻚⾯,通过模板显示准备的数据(如下图)
FreeMarker不是⼀个Web应⽤框架,⽽适合作为Web应⽤框架⼀个组件。
FreeMarker与容器⽆关,因为它并不知道HTTP或Servlet。FreeMarker同样可以应⽤于⾮Web应⽤程序环境。
FreeMarker更适合作为Model2框架(如Struts)的视图组件,你也可以在模板中使⽤JSP标记库。
2.2 FreeMarker特性
[1]通用目标
能够⽣成各种⽂本:HTML、XML、RTF、Java 源代码等等
易于嵌⼊到你的产品中:轻量级;不需要 Servlet 环境
插件式模板载⼊器:可以从任何源载⼊模板,如本地⽂件、数据库等等
可以按你所需⽣成⽂本:保存到本地⽂件;作为 Email 发送;从 Web 应⽤程序发送它返回给 Web 浏览器
[2]强大的模板语言
所有常⽤的指令:include、if/elseif/else、循环结构
在模板中创建和改变变量
⼏乎在任何地⽅都可以使⽤复杂表达式来指定值
命名的宏,可以具有位置参数和嵌套内容
名字空间有助于建⽴和维护可重⽤的宏库,或将⼤⼯程分成模块,⽽不必担⼼名字冲突
输出转换块:在嵌套模板⽚段⽣成输出时,转换HTML转义、压缩、语法⾼亮等等;你可以定义⾃⼰的转换
[3]通用数据模型
FreeMarker不是直接反射到Java对象,Java对象通过插件式对象封装,以变量⽅式在模板中显示
可以使⽤抽象(接⼝)⽅式表示对象(JavaBean、XML⽂档、SQL查询结果集等等),告诉模板开发者使⽤⽅法,使其不受技术细节的打扰
[4]为Web准备
在模板语⾔中内建处理典型Web相关任务(如HTML转义)的结构
能够集成到Model2 Web应⽤框架中作为JSP的替代
⽀持JSP标记库
为MVC模式设计:分离可视化设计和应⽤程序逻辑;分离⻚⾯设计员和程序员
3、基本语法
3.1 注释语法
<!--
html注释语法
浏览器中注释可见
-->
<#--
freemarker 注释语法
浏览器中注释不可见
1.在FreeMarker中,html所有标签均使用
2.js/css均适用,语法一致
-->
${msg}
3.2 数据类型
Freemarker 模板中的数据类型由如下⼏种:
布尔型:等价于 Java 的 Boolean 类型,不同的是不能直接输出,可转换为字符串输出
⽇期型:等价于 java 的 Date 类型,不同的是不能直接输出,需要转换成字符串再输出
数值型:等价于 java 中的 int,float,double 等数值类型,有三种显示形式:数值型(默认)、货币型、百分⽐型
字符型:等价于 java 中的字符串,有很多内置函数
sequence 类型:等价于 java 中的数组,list,set 等集合类型
hash 类型:等价于 java 中的 Map 类型
布尔类型
Java代码
/**
* 布尔类型
*
* @author Promsing(张有博)
* @since 2022/12/31 - 19:27
* @version 1.0.0
*/
@WebServlet("/f02")
public class Freemarker02 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("flag",true);
//转发给ftl文件中
req.getRequestDispatcher("template/fmk02.ftl").forward(req,resp);
}
}
HTML文件
<#--
布尔类型
布尔类型不能在FreeMarker页面输出,需要改成String输出
方式一: ?c,推荐
方式一: ?string('ture','false')
官网:http://freemarker.foofun.cn/ref_builtins_boolean.html
-->
<h2>布尔类型</h2>
<h2>${flag?c}</h2>
<h2>${flag?string}</h2>
<h2>${flag?string('喜欢','不喜欢')}</h2>
日期类型
Java文件
/**
* 日期类型
*
* @author Promsing(张有博)
* @since 2022/12/31 - 19:27
* @version 1.0.0
*/
@WebServlet("/f03")
public class Freemarker03 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("time",new Date());
//转发给ftl文件中
req.getRequestDispatcher("template/fmk03.ftl").forward(req,resp);
}
}
HTML文件
<#--
日期类型
日期类型不能在FreeMarker页面输出,需要改成日期型或者String输出
方式一:年月日 ?date
方式二:时分秒 ?time
方式三:年月日时分秒 ?datetime
方式四:指定格式 ?string("自定义格式")
y:年 M:⽉ d:⽇
H:时 m:分 s:秒
官网:http://freemarker.foofun.cn/ref_builtins_date.html
-->
<h2>日期类型</h2>
<h2>${time?date}</h2>
<h2>${time?time}</h2>
<h2>${time?datetime}</h2>
<h2>${time?string("yyyy年MM月dd日 HH:mm:ss")}</h2>
数值类型
Java文件
/**
* 数值类型
*
* @author Promsing(张有博)
* @since 2022/12/31 - 19:27
* @version 1.0.0
*/
@WebServlet("/f04")
public class Freemarker04 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("age",18);
req.setAttribute("num",10000);
req.setAttribute("avg",0.05688);
//转发给ftl文件中
req.getRequestDispatcher("template/fmk04.ftl").forward(req,resp);
}
}
HTML文件
<#--
数值类型
在FreeMarker中数值类型可以直接输出
1.转字符串
普通字符串 ?c
货币类型字符串 ?string.currency
百分比字符串 ?string.percent
2.保留浮点型数值指定小数位(#表示一个小数位)
?string["0.##"]
官方网址:http://freemarker.foofun.cn/ref_builtins_number.html
-->
<h2>数值类型</h2>
<h2>${age}</h2>
<h2>${num}</h2>
<h2>${avg}</h2>
<h2>普通字符串${num?c}</h2>
<h2>货币类型${num?string.currency}</h2>
<h2>百分比类型${avg?string.percent}</h2>
<h2>小数一位${avg?string["0.##"]}</h2>
字符串类型
Java文件
/**
* 字符串类型
*
* @author Promsing(张有博)
* @since 2022/12/31 - 19:27
* @version 1.0.0
*/
@WebServlet("/f05")
public class Freemarker05 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("str","小小张自由");
req.setAttribute("string","www.BAIDU.com");
//转发给ftl文件中
req.getRequestDispatcher("template/fmk05.ftl").forward(req,resp);
}
}
HTML文件
<#--
字符串类型
在FreeMarker中字符串类型可以直接输出
常用的方法如下
1.截取字符串(左闭右开) ?substring(start,end)
2.首字母小写输出 ?uncap_first
3.首字母大写输出 ?cap_first
4.字母转小写输出 ?lower_case
5.字母转大写输出 ?upper_case
6.获取字符串长度 ?length
7.是否以指定字符开头 (Boolean类型) ?starts_with("xx")?string
8.是否以指定字符结尾 (Boolean类型) ?end_with("xx")?string
9.获取指定字符的索引 ?index_of("xx")
10.去除字符串前后空格 ?trim
11.替换指定字符串 ?replace("xx","yy")
官方文档:http://freemarker.foofun.cn/ref_builtins_string.html
-->
<h2>字符串类型</h2>
<h2>str--${str}</h2>
<h2>string--${string}</h2>
<h2>截取字符串--${str?substring(0,3)}</h2>
<h2>首字符小写--${string?uncap_first}</h2>
<h2>首字符大写--${string?cap_first}</h2>
<h2>字母转小写--${string?lower_case}</h2>
<h2>字母转大写--${string?upper_case}</h2>
<h2>获取字符串长度--${string?length}</h2>
<h2>是否以ww字符开头--${string?starts_with("ww")?string}</h2>
<h2>是否以ww字符结尾--${string?ends_with("ww")?string}</h2>
<h2>获取 . 字符的索引--${string?index_of(".")}</h2>
<h2>去除字符串前后空格--${string?trim}</h2>
<h2>替换指定字符串--${string?replace("www","zzz")}</h2>
<h2>字符串类型</h2>
sequence(序列类型)
包含数组、List集合、Set集合
Java代码
public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* sequence类型
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/12/31 - 19:27
*/
@WebServlet("/f06")
public class Freemarker06 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//数组
String[] strs = new String[]{"周杰伦", "林青霞", "黄飞鸿", "成龙"};
req.setAttribute("strs", strs);
//list集合
List<String> citys = Arrays.asList("上海", "北京", "杭州", "深圳");
req.setAttribute("citys", citys);
List<User> userList = new ArrayList<>();
userList.add(new User(1,"zhangsan",22));
userList.add(new User(2,"lisi",28));
userList.add(new User(3,"wangwu",34));
userList.add(new User(4,"liuliu",44));
req.setAttribute("userList",userList);
//转发给ftl文件中
req.getRequestDispatcher("template/fmk06.ftl").forward(req, resp);
}
}
HTML文件
<#--序列类型(数组/list/set)
通过list指令输出序列
<#list 序列名 as 元素名>
${元素名}
</#list>
获取序列的长度 ${元素名?size}
获取序列元素的下标(循环内使用) ${元素名?index}
获取第一个元素 ${元素名?first}
获取最后一个元素 ${元素名?last}
倒序输出 序列名?reverse
升序输出 序列名?sort
降序输出 序列名?sort?reverse
指定字段名排序 序列名?sort_by("字段名")
注:一般是JavaBean集合
-->
<h2>数组、集合类型</h2>
<h2>数组</h2>
<#list strs as str>
${str?index}--${str}<br>
</#list>
获取序列的长度 ${strs?size} <br>
获取第一个元素 ${strs?first} <br>
获取最后一个元素 ${strs?last} <br>
<h2>--------------</h2>
<h2>集合list</h2>
<#list citys as ty>
${ty?index}--${ty}<br>
</#list>
<h2>集合list实体</h2>
<#list userList as user>
编号:${user.id} 姓名:${user.name} 年龄:${user.age} <br>
</#list>
<#list userList?sort_by("age") as user>
编号:${user.id} 姓名:${user.name} 年龄:${user.age} <br>
</#list>
Hash类型
注意key值只能是String类型,其他类型的话,根据key取value会报错
Java代码
/**
* hash类型
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/12/31 - 19:27
*/
@WebServlet("/f07")
public class Freemarker07 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//hashMap
Map<String , User> map = new HashMap<>();
map.put("1",new User(1,"zhangsan",22));
map.put("2",new User(2,"lisi",28));
map.put("3",new User(3,"wangwu",34));
map.put("4",new User(4,"liuliu",44));
req.setAttribute("userMap",map);
Map<String, String> strMap = new HashMap<>();
strMap.put("11","yi");
strMap.put("22","er");
strMap.put("33","san");
req.setAttribute("strMap",strMap);
//转发给ftl文件中
req.getRequestDispatcher("template/fmk07.ftl").forward(req, resp);
}
}
HTML文件
<#--
hash类型
key遍历输出
经过测试只有String类型能使用这种方式
<#list hash?keys as key>
${key}---${hash[key]}
</list>
value遍历输出
<#list hash?values as value>
${value}
</list>
-->
<h2>hash类型,key遍历输出</h2>
<h2> 经过测试只有String类型能使用这种方式{strMap[key]}</h2>
<#--获取key,value-->
<#list strMap?keys as key>
Key: ${key} ---${strMap[key]} <br>
</#list>
<h2>hash类型,value遍历输出</h2>
<#--获取key-->
<#list userMap?values as value>
编号:${value.id} 姓名:${value.name} 年龄:${value.age} <br>
</#list>
<h2>hash类型,value遍历输出</h2>
<h2>此时Key为String类型</h2>
<#list userMap?keys as key>
Key: ${key} ---${userMap[key].name} <br>
</#list>
3.3 空值处理
FreeMarker空值、null值会报错,空字符串可以
解决办法
_FreeMarker提供两个运算符来避免空值
- !:指定缺失变量的默认值
_ v a l u e ∗ ∗ ! ∗ ∗ ∗ ∗ 如 果 v a l u e 值为空,默认显示空字符 串 ∗ ∗ {value**!**} **_如果value值为空,默认显示空字符串 _** value∗∗!∗∗∗∗如果value值为空,默认显示空字符串∗∗{value!**“默认值”} **_如果value值为空,默认显示"默认值" - ??:判断变量是否存在
如果变量存在,返回true,否则返回false
_${(value??)?string}
<h3>空字符串:${massage!}</h3>
<h3>默认值:${massage!"空字符串变默认值"}</h3>
<h3>判断是否存在:${(massage??)?string}</h3>
4、常用指令
4.1 assign 自定义变量指令
<#--
自定义变量指令 assign
使用assign指令可以创建一个新的变量,或者替换已经存在的变量
语法:
<#assign 变量名=值>
<#assign 变量名=值 变量名=值>定义多个变量
-->
<h2>自定义指令assign</h2>
<#assign str="hello">
${str}<br>
<#assign num=1,names=["zhangsan","lisi","wangwu"]>
${num} --${names?join(".")}
<br>
获取对象中的数据
<#assign mapData={"name":"程序员", "salary":15000}>
${mapData["name"]}
${mapData["salary"]}
4.2 if elseif 逻辑判断指令
<#--
if,else,elseif 逻辑判断指令
<#if condition>
...
<#elseif condition>
...
<#else condition>
...
</#if>
注意:condition将被计算成布尔表达式
elseif,else指令都是可选的
-->
<h2> if,else,elseif 逻辑判断指令</h2>
<#assign score=88>
<#if score lt 60 >
不及格的成绩 ${score}
<#elseif score==60>
及格的成绩 ${score}
<#elseif score gt 60 && score lt 80>
优秀的成绩 ${score}
<#else>
接近满分的成绩 ${score}
</#if>
<h2>判断数据是否存在</h2>
<#assign list="">
<#if list??>
list存在
<#else>
list不存在
</#if>
<#if set??>
set存在
<#else>
set不存在
</#if>
4.3 list 遍历指令
<#--
list指令
格式1
<#list sequence as item>
${item}
</#list>
格式1
<#list sequence as item>
${item}
<#else>
当没有选项是执行else
</#list>
-->
<#assign list=["lisi","shangsan","liuliu"]>
<h3>list指令-循环</h3>
<#list list as item>
${item} <br>
</#list>
<h3>if-list指令</h3>
<h3>相当于空指针判断</h3>
<#if map??>
<#list map as mm>
${mm} <br>
</#list>
</#if>
<#assign set=[]>
<h3>list-else指令</h3>
<#list set as item>
${item} <br>
<#else>
输出默认值
</#list>
4.4 marco自定义指令
<#--
自定义指令
1.基本使用:
格式:
<#macro 指令名>
指令内容
</#macro>
使用:
<@指令名></@指令名>
2.有参数的自定义指令
注意:指令可以多次使用
自定义指令可以包含字符串,也可以包含内置指令
-->
声明指令<br>
<#macro address>
河北省邯郸市大名县黄金堤乡
</#macro>
使用:
<@address></@address>
<hr>
声明 自定义有参数指令<br>
<#macro queryUserByName uname>
通过用户名查询对象 ${uname}
</#macro>
使用:
<@queryUserByName uname="admin"></@queryUserByName>
<hr>
声明 自定义多参数指令<br>
<#macro queryUserByName2 uname upwd uphone>
通过用户名查询对象 ${uname} -- ${upwd} --${uphone}
</#macro>
使用:
<@queryUserByName2 uname="admin" upwd="123" uphone="1508888"></@queryUserByName2>
4.5 nested 占位指令
<#--
nested 占位指令
nested 相当于占位符,一般结合macro指令一起使用
(自我感觉像是传参数的自定义指令)
可以将自定义指令中的内容通过nested指令占位,
当使用自定义指令时,会将占位内容显示
-->
<#macro test>
这是一个文本!!!,即将要被插--------
<br>
<#nested>
<#nested>
</#macro>
<@test>这是插入的内容</@test>
4.6 import指令与include指令
** 导入指令 import**
在ftl文件中引入命名空间,可以使用引入的空间的宏
** 包含指令 include**
可以包含其他页面文件,比如说html,ftl,txt
<#--
导入指令 import
在ftl文件中引入命名空间,可以使用引入的空间的宏
包含指令 include
可以包含其他页面文件,比如说html,ftl,txt
-->
<#-- 导⼊命名空间 -->
<#import "commons.ftl" as common>
<#-- 使⽤命名空间中的指令 -->
<@common.cfb num=5></@common.cfb>
<#--包含指令(引⼊其他⻚⾯⽂件) include-->
<#--html⽂件-->
<#include "hello.html">
<#--freemarker⽂件-->
<#include "fmk12.ftl">
<#--txt⽂件-->
<#include "test.txt">
其他文件
此文件定义通用的宏 macro
<#macro cfb num>
<#list 1..num as i>
<#list 1..i as j>
${j}*${i}=${j*i}
</#list>
<br>
</#list>
</#macro>
<#macro zyb>
<h2>我是张有博</h2>
</#macro>
5、页面静态化
通过上述介绍可知 Freemarker 是⼀种基于模板的、⽤来⽣成输出⽂本的通⽤⼯具,所以我们必须要定制符合⾃⼰业务的模板,然后⽣成⾃⼰的 html ⻚⾯。Freemarker 是通过freemarker.template.Configuration 这个对象对模板进⾏加载的(它也处理创建和缓存预解析模板的⼯作),然后我们通过 getTemplate ⽅法获得你想要的模板,有⼀点要记住freemarker.template.Configuration 在你整个应⽤必须保证唯⼀实例。
5.1 定义模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面静态化</title>
</head>
<body>
<h2 align="center" >${title}</h2>
<p align="center">
新闻来源:${source}
发布时间:${pubTime?datetime}
</p>
<p style="text-indent: 2em">
${content}
</p>
</body>
</html>
5.2 Java文件
/**
* 页面静态化
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2023/1/2 - 15:52
*/
@WebServlet("news")
public class NewsServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 实例化模板配置对象
Configuration configuration = new Configuration();
// 设置加载模板的上下⽂ 以及 设置加载模板路径(模板存放的路径)
configuration.setServletContextForTemplateLoading(getServletContext(),"/template");
// 设置模板的编码格式
configuration.setDefaultEncoding("UTF-8");
// 加载模板⽂件,获取模板对象
Template template = configuration.getTemplate("news.ftl");
// 设置模型数据
Map<String,Object> map = new HashMap<>();
map.put("title", "特别就业季:稳就业情况如何? 哪些问题待解?");
map.put("source", "⼈⺠⽇报");
map.put("pubTime", new Date());
map.put("content", "中共中央政治局常务委员会近⽇召开会议强调," +
"要有针对性地开展援企、稳岗、扩就业⼯作," +
"做好⾼校毕业⽣、农⺠⼯等重点群体就业⼯作," +
"积极帮助个体⼯商户纾困。疫情期间,稳就业情况如何?还有哪些问题待解?" +
"记者采访了不同群体,记录这个特别的就业季。");
// 获取项⽬所在的根⽬录
String basePath = req.getServletContext().getRealPath("/");
// 设置⻚⾯存放的⽬录
File htmlFile = new File(basePath + "/html");
// 判断⽬录是否存在
if (!htmlFile.exists()) {
// 如果⽬录不存在,则新建⽬录
htmlFile.mkdir();
}
// 获取⽂件名(随机⽣成不重复的⽂件名)
String fileName = System.currentTimeMillis() + ".html";
// 创建html⽂件
File file = new File(htmlFile, fileName);
// 获取⽂件输出流
FileWriter writer = new FileWriter(file);
try {
// 输出html 将模型数据填充到模板中
template.process(map, writer);
// 输出成功
System.out.println("新闻创建成功!");
} catch (TemplateException e) {
e.printStackTrace();
} finally {
writer.flush();
writer.close();
}
}
}
5.3 生成HTML文件
浏览器地址栏输⼊: http://localhost:端口号/news
⽣成的⽂件存放在当前项⽬的webapp⽬录下的html⽬录中。
6、运算符
6.1 算术运算符
<#--
算术运算符
+、-、*、/、%
${a1+a2},只有在{}内才能生效
-->
<#assign a1=8 a2=2>
${a1}+${a2}=${a1+a2}<br>
${a1}-${a2}=${a1-a2}<br>
${a1}*${a2}=${a1*a2}<br>
${a1}/${a2}=${a1/a2}<br>
${a1}%${a2}=${a1%a2}<br>
<!--字符串运算-->
${"hello"+","+"FreeMarker"}
6.2 逻辑运算符
<#--
逻辑运算符
&&、\\、!
-->
6.3 比较运算符
<#--
比较运算符
> (gt):大于号,推荐使用 gt
< (lt):小于号,推荐使用 lt
>= (gte):大于等于,推荐使用 gte
<= (lte):小于等于,推荐使用 lte
==:等于
!=:不等于
-->
6.4 空值运算符
<#--
空值运算符
1.??判断是否为空,返回布尔类型
如果不为空返回false,如果为空返回true,不能直接输出
${(name??)?string}
2.!: 设置默认值,如果为空,则设置默认值
1.设置默认为空字符串 ${name!}
2.设置默认值 ${name!'张三'}
-->