五、FreeMarker
5.1 FreeMarker介绍
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNM7dglY-1650637129688)(JavaEE.assets/FreeMarker.png)]
这种方式通常被称为 MVC (模型 视图 控制器) 模式,对于动态网页来说,是一种特别流行的模式。 它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。
而FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到 Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。
5.2 FreeMarker的特性
5.2.1 通用目标
能够生成各种文本:HTML、XML、RTF、Java 源代码等等
易于嵌入到你的产品中:轻量级;不需要 Servlet 环境
插件式模板载入器:可以从任何源载入模板,如本地文件、数据库等等
你可以按你所需生成文本:保存到本地文件;作为 Email 发送;从 Web 应用程序发送它返回给 Web浏览器
5.2.2强大的模板语言
所有常用的指令:include、if/elseif/else、循环结构
在模板中创建和改变变量
几乎在任何地方都可以使用复杂表达式来指定值
命名的宏,可以具有位置参数和嵌套内容
名字空间有助于建立和维护可重用的宏库,或将大工程分成模块,而不必担心名字冲突
输出转换块:在嵌套模板片段生成输出时,转换HTML转义、压缩、语法高亮等等;你可以定义自己
的转换
5.2.3通用数据模型
FreeMarker不是直接反射到Java对象,Java对象通过插件式对象封装,以变量方式在模板中显示
你可以使用抽象(接口)方式表示对象(JavaBean、XML文档、SQL查询结果集等等),告诉模板开 发者使用方法,使其不受技术细节的打扰
5.2.4 为Web准备
在模板语言中内建处理典型Web相关任务(如HTML转义)的结构
能够集成到Model2 Web应用框架中作为JSP的替代
支持JSP标记库
为MVC模式设计:分离可视化设计和应用程序逻辑;分离页面设计员和程序员
5.2.5智能的国际化和本地化
字符集智能化(内部使用UNICODE)
数字格式本地化敏感
日期和时间格式本地化敏感
非US字符集可以用作标识(如变量名)
多种不同语言的相同模板
5.2.6强大的XML处理能力
<#recurse> 和 <#visit> 指令(2.3版本)用于递归遍历XML树。在模板中清楚和直接的访问XML对象
模型。开源论坛 JForum 就是使用了 FreeMarker 做为页面模板。
5.3 FreeMarker环境搭建步骤
-
新建一个Maven Web项目
-
配置坐标依赖和部署插件
- 在项目的webapp/WEB-INF目录下的web.xml文件中,添加freemarker 相关 servlet 配置
<!-- freemarker的坐标依赖 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency> <!-- servlet-api的坐标依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency>
-
修改配置文件web.xml
- 在项目的webapp/WEB-INF目录下的web.xml文件中,添加freemarker 相关 servlet 配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <!-- FreeMarker 的Servlet配置 -->
<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
<init-param> <!-- 模板路径 -->
<param-name>TemplatePath</param-name> <!-- 默认在webapp目录下查找对应的模板文件 -->
<param-value>/</param-value>
</init-param>
<init-param> <!-- 模板默认的编码:UTF-8 -->
<param-name>default_encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 2.3.4. 编写Servlet类 2.3.5. 新建模板文件 ftl-->
<!-- 在webapp目录下新建template文件夹,创建f01.ftl文件-->
<!-- 2.3.6. 启动项目-->
</servlet> <!-- 处理所有以.ftl结尾的文件;ftl是freemarker默认的文件后缀 -->
<servlet-mapping>
<servlet-name>freemarker</servlet-name>
<url-pattern>*.ftl</url-pattern>
</servlet-mapping>
</web-app>
-
编写Servlet 类
@WebServlet("/f1") public class FreeMarker extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //创建域对象属性 req.setAttribute("mgr","hello"); //跳转到页面中 req.getRequestDispatcher("TemplatePath/freemarker01.ftl").forward(req,resp); } }
-
新建模板文件 ftl
- 在webapp目录下新建template文件夹,创建freemarker01.ftl文件
<#--ftl文件内容--> ${mgr}
-
启动项目
-
访问项目
5.4 FreeMarker 的数据类型
- 布尔型:
- 等价于 Java 的 Boolean 类型,不同的是不能直接输出,可转换为字符串输出
- 日期型:
- 等价于 java 的 Date 类型,不同的是不能直接输出,需要转换成字符串再输出
- 数值型:
- 等价于 java 中的 int,float,double 等数值类型
- 三种显示形式:数值型(默认)、货币型、百分比型
- 字符型:
- 等价于 java 中的字符串,有很多内置函数
- sequence 类型:
- 等价于 java 中的数组,list,set 等集合类型
- hash 类型:
- 等价于 java 中的 Map 类型
5.4.1 布尔类型
boolean类型的数据是不能直接输出,需要转换成字符串使用
5.4.2 日期类型
在freemarker中日期类型不能直接输出;如果输出要先转成日期型或字符串
-
转换规则:
1. 年月日 ?date 2. 时分秒 ?time 3. 年月日时分秒 ?datetime 4. 指定格式 ?string("自定义格式") y:年 M:月 d:日 H:时 m:分 s:秒
-
示例:
// 在servlet中设置日期类型 request.setAttribute("createDate",new Date());
<#-- 在.ftl文件中获取数据 --> <#-- 输出日期格式 --> ${createDate?date} <br> <#-- 输出时间格式 --> ${createDate?time} <br> <#-- 输出日期时间格式 --> ${createDate?datetime} <br> <#-- 输出格式化日期格式 --> ${createDate?string("yyyy年MM月dd日 HH:mm:ss")} <br>
5.4.3 数值类型
数值类型在freemarker中数值类型可以直接输出;
-
转换规则:
1. 转字符串 普通字符串 ?c 货币型字符串 ?string.currency 百分比型字符串 ?string.percent 2. 保留浮点型数值指定小数位(#表示一个小数位) ?string["0.##"]
-
示例:
// 在servlet中设置数值类型 req.setAttribute("age",18); // 数值型 req.setAttribute("salary",10000); // 数值型 req.setAttribute("avg",0.545); // 浮点型
<#-- 在.ftl文件中获取数据 --> <#-- 直接输出数值型 --> ${age} <br> ${salary} <br> <#-- 将数值转换成字符串输出 --> ${salary?c} <br> <#-- 将数值转换成货币类型的字符串输出 --> ${salary?string.currency} <br> <#-- 将数值转换成百分比类型的字符串输出 --> ${avg?string.percent} <br> <#-- 将浮点型数值保留指定小数位输出 (##表示保留两位小数) --> ${avg?string["0.##"]} <br>
5.4.4 字符串类型
-
转换规则:
直接输出,常用方法如下
在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类型) ?ends_with("xx")?string 9. 获取指定字符的索引 ?index_of("xx") 10. 去除字符串前后空格 ?trim 11. 替换指定字符串 ?replace("xx","xx")
-
示例:
// 在servlet中设置数值类型
request.setAttribute("msg","Hello ");
request.setAttribute("msg2","freemarker");
<#-- 在.ftl文件中获取数据 -->
<#-- 直接输出 -->
${msg} - ${msg2} <br>
<#--1. 截取字符串(左闭右开) ?substring(start,end)-->
${msg?substring(0,2)}<br>
<#--2. 首字母小写输出 ?uncap_first-->
${msg?uncap_first}<br>
<#--3. 首字母大写输出 ?cap_first-->
${msg2?cap_first}<br>
<#--4. 字母转小写输出 ?lower_case-->
${msg?lower_case}<br>
<#--5. 字母转大写输出 ?upper_case-->
${msg2?upper_case}<br>
<#--6. 获取字符串长度 ?length-->
${msg2?length}<br>
<#--7. 是否以指定字符开头(boolean类型) ?starts_with("xx")?string-->
${msg?starts_with("H")?c}<br>
<#--8. 是否以指定字符结尾(boolean类型) ?ends_with("xx")?string-->
${msg2?ends_with("e")?c}<br>
<#--9. 获取指定字符的索引 ?index_of("xx")-->
${msg2?index_of("r")}<br>
<#--10. 去除字符串前后空格 ?trim-->
${msg2?trim}<br>
<#--11. 替换指定字符串 ?replace("xx","xx")-->
${msg2?replace("ker","abc")}<br>
5.4.5 sequence类型
在Servlet中设置序列类型(数组、List、Set)的数据
-
List格式:
<#list 序列名 as 元素名> ${名称} </#list>
-
序列类型常用方法
获取序列的长度 ${序列名?size} 获取序列元素的下标 ${元素名?index} 获取第一个元素 ${序列名?first} 获取最后一个元素 ${序列名?last} 倒序输出 序列名?reverse 升序输出 序列名?sort 降序输出 序列名?sort?reverse 指定字段名排序 序列名?sort_by("字段名")
-
示例:
// 在servlet中设置数值类型 // 数组操作 String[] stars = new String[]{"周杰伦","林俊杰","陈奕迅","五月天"}; request.setAttribute("stars",stars); // List操作 List<String> citys = Arrays.asList("上海","北京","杭州","深圳"); request.setAttribute("cityList",citys); // JavaBean集合 List<User> userList = new ArrayList<>(); userList.add(new User(1,"zhangsan",22)); userList.add(new User(2,"lisi",18)); userList.add(new User(3,"wangwu",20)); request.setAttribute("userList",userList);
<#-- 数组操作 --> <#list stars as star> 下标:${star?index} -- 名字:${star} <br> </#list> 数组的长度:${stars?size} <br> <#-- 获取第一个元素 --> 第一个元素:${stars?first} <br> <#-- 获取最后一个元素 --> 最后一个元素:${stars?last} <br> <hr> <#-- List操作 --> <#list cityList as city > ${city} <br> </#list> List的size:${cityList?size} <br> <#-- 倒序输出 --> <#list cityList?reverse as city > ${city} - </#list> <br> <#-- 升序输出 --> <#list cityList?sort as city > ${city} - </#list> <br> <#-- 降序输出 --> <#list cityList?sort?reverse as city >
5.4.6 hash 类型
-
格式:
key遍历输出 <#list hash?keys as key> ${key} -- ${hash[key]} </#list> value遍历输出 <#list hash?values as value> ${value} </#list>
-
示例:
// Map操作 Map<String,String> cityMap = new HashMap<>(); cityMap.put("sh","上海"); cityMap.put("bj","北京"); cityMap.put("sz","深圳"); request.setAttribute("cityMap",cityMap);
<#-- key遍历输出 --> <#list cityMap?keys as key> ${key} -- ${cityMap[key]} <br> </#list> <#-- value遍历输出 --> <#list cityMap?values as value> ${value} </#list>
5.5 FreeMarker 常用指令
5.5.1自定义变量指令
使用 assign
指令你可以创建一个新的变量, 或者替换一个已经存在的变量。
-
语法:
-
<#assign 变量名=值>
-
<#assign 变量名=值 变量名=值> (定义多个变量)
-
-
示例
<#-- 直接在.ftl文件中定义后直接运行输出--> <#assign str="hello"> ${str} <br> <#assign num=1 names=["zhangsan","lisi","wangwu"] > ${num} -- ${names?join(",")}
5.5.2 if elseif else 逻辑判断指令
可以使用 if , elseif 和 else 指令来条件判断是否满足某些条件。
-
格式:
<#if condition> ... <#elseif condition2> ... <#else> ... </#if>
- 注意
- condition, condition2等:将被计算成布尔值的表达式。
- elseif 和 else 指令 是可选的。
- elseif中的比较运算符 > 改成 gt <改成 lt
- 注意
-
示例:
<#assign score =99> <#if score<60 > 不及格 <#elseif score gt 60 && score lt 80 > 及格 <#else> 优秀 </#if>
5.5.3 list遍历指令
-
格式1:
<#list sequence as item>
</#list>
-
格式2:
<#list sequence as item>
<#else>
当没有选项时,执行else指令
</#list>
-
注意:
- else 部分是可选的
- sequence: 想要迭代的项,可以是序列或集合的表达式
- item: 循环变量 的名称
- 当没有迭代项时,才使用 else 指令, 可以输出一些特殊的内容而不只是空在那里
-
示例:
<#assign users = ["张三","李四","王五"]> <#-- 遍历序列 --> <#list users as user> ${user} </#list>
5.5.4 macro 自定义指令 宏
自定义指令需要先定义,再使用(用@来使用)
- 基本使用定义:
-
定义格式:
<#macro 指令名>
指令内容
</#macro>
-
使用:
<@指令名>< /@指令名>
- 带有参数的自定义指令
-
格式:
<#macro 指令名 参数名1 参数名2>
指令内容
</#macro>
-
使用:
<@指令名 参数名1=参数值1 参数名2=参数值2>< /@指令名 >
-
注意:
-
指令可以被多次使用。
-
自定义指令中可以包含字符串,也可包含内置指令
-
-
示例:
<#--宏 自定义指令--> <#macro test> <h4>test指令</h4> </#macro> <#--宏 自定义指令 一个参数的宏定义方法--> <#macro printCFB num> <#list 1..num as i> <#list 1..i as j> ${j} * ${i} = ${i*j} </#list> <br> </#list> </#macro> <#--宏 自定义指令 多参数--> <#macro pr num1 num2> ${num1}--${num2} </#macro> <#--宏 自定义指令的使用--> <@test></@test> <@printCFB 5></@printCFB> <@pr num1=1 num2=3></@pr>
5.5.5 nested占位指令
nested
相当于占位符,一般结合macro指令一起使用。
可以将自定义指令中的内容通过nested指令占位,当使用自定义指令时,会将占位内容显示。
<#macro test>
这是一段文本!
<#nested>
<#nested>
</#macro>
<#--使用test宏-->
<@test>
<h4>这是文本后面的内容!</h4>
</@test>
5.5.6 import导入指令
import 指令可以引入一个库。也就是说,它创建一个新的命名空间, 然后在那个命名空间中执行给定路径的模板。可以使用引入的空间中的指令。
在其他ftl页面中通过import导入你想要页面的.ftl的模板,然后就可以使用该命名空间中的指令
-
示例:
<#--import导入--> <#import "freemarker03.ftl" as zl> <@zl.printCFB 3></@zl.printCFB>
通过导入
freemarker03.ftl
的路径,就可以使用该路径下的模板内容以及自定义的宏的方法
5.5.7 include包含标签
可以使用 include 指令在你的模板中插入另外一个 FreeMarker 模板文件 。 被包含模板的输出格式是在 include 标签出现的位置插入的。 被包含的文件和包含它的模板共享变量,就像是被复制粘贴进去的一样。
-
示例:
<#--包含指令(引入其他页面文件) include--> <#--html文件--> <#include "test.html"> <#--freemarker文件--> <#include "test.ftl"> <#--text文件--> <#include "test.txt">