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!'张三'}
-->



















