springboot整合freemarker

前言

本篇文章主要介绍的是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

原文地址:https://www.cnblogs.com/lixianguo/p/12519012.html