前言
本篇文章主要介绍的是springboot整合freemarker填充ftl模板文件,生成新的文件(如html),以及freemarker的语法。
GitHub源码链接位于文章底部。
freemarker介绍
freemarker是一款模板引擎,它基于模板来生成文本输出。这里的文本包括但不限于html页面,word,各种源代码文本......
工作原理
模板:就是一份已经写好了基本内容,有着固定格式的文档,其中空出或者用占
位符标识的内容,由使用者来填充,不同的使用者给出的数据是不同的。在模板
中的占位符,在模板运行时,由模板引擎来解析模板,并采用动态数据替换占位
符部分的内容。
freemarker的应用方向有两个,一是基于ftl文件,将内容填充到ftl文件中,就可以使用制作ftl模板的文本的方式进行访问和显示了,比如使用html文本制作了一个ftl模板,我们使用代码填充数据进ftl模板,那么我们就能以访问html的方式去打开这个文件了;另一种方式则是直接生成对应的文件,比如生成xxx.html的文件。
应用场景:
淘宝中的商品数不胜数,在商品的详情页这一块,如果全都以真实的html页面显示,那么有多少个商品就得有多少个页面了,何况还有增删改的情况。所以使用一个固定的ftl模板,填充数据,这样一个文件就能显示无数个页面的内容了。
再比如一些政府单位的项目,每天需要发送一些word文档给领导,此时只需要通过程序将数据填充进ftl模板,然后生成一个个的xxx.word文件就行了。
ftl指令
1.一些常见的符号说明:
${}插值; 只能输出数值、日期或者字符串,其它类型不能输出。
在ftl页面中添加如下代码:
<#--这是 freemarker 注释,不会输出到文件中-->
<h1>${name}; ${message}</h1>
在freemarker接口里添加数据如下:
这一段代码在页面输出的就是:
<#freemarker 命令
<#-- 注释 -->
<@使用自定义命令
??是判断对象是否存在
?函数调用
2.assign
此指令用于在页面上定义一个变量。 语法: <#assign name=value>
<#--assign-->
<#--简单类型-->
<#assign linkman="李四"/>
联系人:${linkman}
<#assign info={"tel":"110","sex":"男"}/>
电话:${info.tel};性别:${info.sex}
页面输出如下:
3. include
语法: <#include path>
说明: path 参数可以是如 "foo.ftl"和 "../foo.ftl"一样的相对路径,或者是如"/foo.ftl"这样的绝对路径。
创建模板文件 header.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>嵌入页面</title>
</head>
<body>
<h1>嵌入页面</h1>
</body>
</html>
在index模板文件中使用 include 指令引入 header.ftl 模板文件
<#--include-->
<#include "header.ftl"/>
访问接口,在index.ftl视图如下:
4.if
<#if condition>
...
<#elseif condition2>
...
<#elseif condition3>
...
...
<#else>
...
</#if>
这里:
condition , condition2 等:表达式将被计算成布尔值。
关键字: gt :比较运算符“大于”; gte :比较运算符“大于或等于”; lt :比较
运算符“小于”; lte :比较运算符“小于或等于”
<#--if-->
<#assign bool=false/>
<#if bool>
bool为true
<#else>
bool为false
</#if>
这里定义一个bool为false,然后使用if进行判断,最后输出为false,
页面显示为:bool为false
5.list
语法:
<#list sequence as item>
...
</#list>
这里:
sequence :表达式将被算作序列或集合
item :循环变量(不是表达式)的名称
如果想在循环中得到索引,使用循环变量+_index 就可以得到。如上述语法中则可以使用 item_index 可以得到循环变量
在freemarker接口里添加数据如下:
在index.ftl中遍历
<#--list-->
<#list goodsList as fruit>
索引:${fruit_index},
水果:${fruit.name};
价格:${fruit.price}
</#list>
页面显示为:
6.内建函数
6.1使用 size 函数来实现对于集合大小的获取:
<#--获取集合总记录数-->
总共${goodsList?size}条记录
<#--集合中索引为1的name属性-->
${goodsList[1].name}
<#--集合中第一个元素的name属性-->
${goodsList?first.name}
显示如下:“总共2条记录 香蕉 苹果 ”
6.2可以使用 eval 将 json 字符串转换为对象
<#assign str="{\'id\':\'123\',\'text\':\'文本\'}"/>
<#assign jsonObj=str?eval/>
id为:${jsonObj.id};text为:${jsonObj.text}
显示如下:“id为:123;text为:文本”
6.3日期格式化
在freemarker接口里添加数据如下:
dataModel.put("today", new Date());
ftl页面添加:
当前日期:${today?date}<br>
<hr>
当前时间:${today?time}<br>
<hr>
当前日期+时间:${today?datetime}
<hr>
格式化显示当前日期时间:${today?string(\'yyyy年MM月dd日 HH:mm:ss\')}
显示如下:
6.4数字转换为字符串
在freemarker接口里添加数据如下:
dataModel.put("number", 123456789L);
index.ftl模板中添加:
<#--数值显示处理-->
${number}
显示如下:“123,456,789”
发现数字会以每三位一个分隔符显示,有些时候不需要这个分隔符,就需要将数
字转换为字符串,使用内建函数 c
${number?c}
显示如下:“123456789”
7.空值处理
在 FreeMarker 中对于空值必须手动处理。
${emp.name!} 表示 name 为空时什么都不显示
${emp.name!(“名字为空”)} 表示 name 为空时显示 名字为空
${(emp.company.name)!} 表示如果 company 对象为空则什么都不显示, !只用
在最近的那个属性判断;所以如果遇上有自定义类型(导航)属性时,需要使用括号
${bool???string} 表示:首先??表示判断 bool 变量是否存在,存在返回 true 否则 false,然后对返回的值调用其内置函数 string
<#if str??> 表示去判断 str 变量是否存在,存在则 true,不存在为 false
${strs!"strs空值的默认显示值"}
<hr>
<#if strs??>
str变量存在
<#else >
strs变量不存在
</#if>
显示如下:
8.运算符
8.1算数运算符
FreeMarker 表达式中完全支持算术运算,FreeMarker 支持的算术运算符包括:+, - ,
*, / , %
8.2逻辑运算符
逻辑运算符有如下几个:
逻辑与:&&
逻辑或:||
逻辑非:!
逻辑运算符只能作用于布尔值,否则将产生错误
8.3. 比较运算符
表达式中支持的比较运算符有如下几个:
1 =或者==:判断两个值是否相等.
2 !=:判断两个值是否不等.
3 >或者 gt:判断左边值是否大于右边值
4 >=或者 gte:判断左边值是否大于等于右边值
5 <或者 lt:判断左边值是否小于右边值
6 <=或者 lte:判断左边值是否小于等于右边值
=和!=可以用于字符串,数值和日期来比较是否相等,但=和!=两边必须是相
同类型的值,否则会产生错误,而且 FreeMarker 是精确比较,"x","x ","X"
是不等的.其
它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用 gt
等字母运算符代替>会有更好的效果,因为 FreeMarker 会把>解释成 FTL 标签的结
束字符,当然,也可以使用括号来避免这种情况,如:<#if (x>y)>
springboot整合freemarker
首先来看一下项目结构:
创建一个springboot项目
pom文件中添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
application.yml中添加freemarker的相关配置
server:
port: 8080
spring:
freemarker:
#设置编码格式
charset: utf-8
#设置文件后缀
suffix: .ftl
#设置ftl文件路径
template-loader-path: classpath:/templates
#关闭缓存,及时刷新,上线生产环境需要改为true
cache: false
在resources目录中添加templates文件夹因为这在yml中配置了。
template文件夹中添加两个ftl模板文件,待会儿会用到,因为是创建html的文件,所以直接新建一个html文件,再将后缀改为ftl就行了。
index.ftl代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Freemarker 测试</title>
</head>
<body>
<#--这是 freemarker 注释,不会输出到文件中-->
<h1>${name}; ${message}</h1>
<hr>
<#--assign-->
<#--简单类型-->
<#assign linkman="李四"/>
联系人:${linkman}
<#assign info={"tel":"110","sex":"男"}/>
电话:${info.tel};性别:${info.sex}
<hr>
<#--include-->
<#include "header.ftl"/>
<hr>
<#--if-->
<#assign bool=false/>
<#if bool>
bool为true
<#else>
bool为false
</#if>
<hr>
<#--list-->
<#list goodsList as fruit>
索引:${fruit_index},
水果:${fruit.name};
价格:${fruit.price}<br>
</#list>
<hr>
<#--获取集合总记录数-->
总共${goodsList?size}条记录
<#--集合中索引为1的name属性-->
${goodsList[1].name}
<#--集合中第一个元素的name属性-->
${goodsList?first.name}
<hr>
<#assign str="{\'id\':\'123\',\'text\':\'文本\'}"/>
<#assign jsonObj=str?eval/>
id为:${jsonObj.id};text为:${jsonObj.text}<p></p>
<hr>
当前日期:${today?date}<br>
<hr>
当前时间:${today?time}<br>
<hr>
当前日期+时间:${today?datetime}
<hr>
格式化显示当前日期时间:${today?string(\'yyyy年MM月dd日 HH:mm:ss\')}
<hr>
<#--数值显示处理-->
${number}
<hr>
${number?c}<p></p>
<hr>
${strs!"strs空值的默认显示值"}
<hr>
<#if strs??>
str变量存在
<#else >
strs变量不存在
</#if>
</body>
</html>
header.ftl代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>嵌入页面</title>
</head>
<body>
<h1>嵌入页面</h1>
</body>
</html>
在freemarker接口中添加代码,返回freemarker的index.ftl视图
@RequestMapping("/freemarkerIndex")
public String freemarker(Map<String, Object> dataModel) {
dataModel.put("name", "张三");
dataModel.put("message", "hello world");
List<Map<String,Object>> list = new ArrayList<>();
Map<String, Object> map1 = new HashMap<>();
map1.put("name", "苹果");
map1.put("price", 4.5);
Map<String, Object> map2 = new HashMap<>();
map2.put("name", "香蕉");
map2.put("price", 6.3);
list.add(map1);
list.add(map2);
dataModel.put("goodsList", list);
dataModel.put("today", new Date());
dataModel.put("number", 123456789L);
return "index";
}
ps:这里dataModel的map只能从方法参数中获取,如果是自己new的去存储数据,填充到模板中,会报空值异常。
启动程序,通过localhost:8080/freemarkerIndex 访问该接口,得到页面如下:
上面这个接口是直接返回视图,
接下来是通过同样的数据,生成index.html静态文件。
因为这里是不用返回的,如果做成接口的话,虽然能成功生成文件,但是会报404,所以这里用的是单元测试,实际开发中,如果是返回的json格式,而不是视图,就可以做成接口。
@Test
public void createFileByFreemarker() throws IOException, TemplateException {
//创建配置对象
Configuration configuration = new Configuration(Configuration.getVersion());
//设置默认生成文件编码
configuration.setDefaultEncoding("utf-8");
//设置模板路径
configuration.setClassForTemplateLoading(this.getClass(), "/templates");
//获取模板
Template template = configuration.getTemplate("index.ftl");
//加载数据
Map<String, Object> dataModel =new HashMap<>();
dataModel.put("name", "张三");
dataModel.put("message", "hello world");
List<Map<String,Object>> list = new ArrayList<>();
Map<String, Object> map1 = new HashMap<>();
map1.put("name", "苹果");
map1.put("price", 4.5);
Map<String, Object> map2 = new HashMap<>();
map2.put("name", "香蕉");
map2.put("price", 6.3);
list.add(map1);
list.add(map2);
dataModel.put("goodsList", list);
dataModel.put("today", new Date());
dataModel.put("number", 123456789L);
//创建输出对象,将文件输出到D盘根目录下
FileWriter fileWriter = new FileWriter("D:/index.html");
//渲染模板和数据
template.process(dataModel, fileWriter);
//关闭输出
fileWriter.close();
}
本文GitHub源码:https://github.com/lixianguo5097/springboot/tree/master/springboot-freemarker
CSDN:https://blog.csdn.net/qq_27682773
简书:https://www.jianshu.com/u/e99381e6886e
博客园:https://www.cnblogs.com/lixianguo
个人博客:https://www.lxgblog.com