侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 748 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

模板引擎Freemarker探究

zze
zze
2019-10-26 / 0 评论 / 0 点赞 / 367 阅读 / 15153 字

介绍

Freemarker 是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。
Freemarker 是免费的,基于 Apache 许可证 2.0 版本发布。其模板语言为 FreeMarker Template Language(FTL),属于简单、专用的语言。需要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,主要用于如何展现数据, 而在模板之外注意于要展示什么数据。

常用的 Java 模板引擎还有哪些?

Jsp、Freemarker、Thymeleaf 、Velocity 等。

模板+数据模型=输出

Freemarker 并不关心数据的来源,只是根据模板的内容,将数据模型在模板中显示并输出文件(通常为 html,也可以生成其它格式的文本文件)。

快速入门

默认情况下 SpringMVC 支持 Freemarker 视图格式,这里就创建 Spring Boot+Freemarker 工程用于测试。

创建测试工程

创建一个 Spring Boot 的测试工程专门用于 Freemarker 的功能测试,添加依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐freemarke</artifactId>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
</dependency>

配置文件

配置 application.ymllogback-spring.xml,从 cms 工程拷贝这两个文件,进行更改,logback-spring.xml 无需更改,application.yml 内容如下:

server:
  port: 8088
spring:
  application:
    name: freemarker-test
  freemarker:
    # 设置模板后缀名
    suffix: .ftl
    # 设置文档类型
    content-type: text/html
    # 设置页面编码格式
    charset: UTF-8
    # 关闭缓存,及时刷新,上线生产环境需要修改为true
    cache: false
    # 设定 ftl 文件路径
    template-loader-path: classpath:/templates

创建模型类

创建模型类用于测试:

package xyz.zze.study.freemarker.bean;

import lombok.Data;
import lombok.ToString;

import java.util.Date;
import java.util.List;

@Data
@ToString
public class Student {

    private String name; // 姓名
    private Integer age; // 年龄
    private Date birthday; // 生日
    private Float money; // 钱包
    private List<Student> friends; // 好友列表
    private Student bestFriend; // 最好的朋友
}

创建模板

src/main/resources 下创建 templates 目录,此目录为 freemarker 的默认模板存放目录。 在 templates 下创建模板文件 test.ftl,模板中的 ${name} 终会被 freemarker 替换成具体的数据。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Freemarker</title>
</head>
<body>
Hello ${name}
</body>
</html>

创建控制器

创建 Controller 类,向 Map 中添加 name 后返回模板文件。

package xyz.zze.study.freemarker.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

@RequestMapping("/freemarker")
@Controller
public class TestController {

    @GetMapping("/test1")
    public String freemarker(Map<String, Object> map) {
        map.put("name", "zze");
        // 返回 src/resources/templates 下的模板名称
        return "test";
    }
}

测试

请求 localhost:8088/freemarker/test1,响应如下:

image.png

Freemarker使用

核心指令

在学习指令前需先了解 Freemarker 的如下知识点:

  • 注释:即 <#-- --> ,介于其之间的内容会被 Freemarker 忽略;
  • 插值(Interpolation):即使用 ${ } 来引用对象,Freemarker 会用对象真实的值代替 ${ }
  • FTL 指令:与 OHTML 标记类似,变量名前加 # 予以区分,Freemarker 会解析标签中的表达式或逻辑;
  • 文本:仅文本信息,直接输出内容。

修改控制器,往 map 中填充数据用于测试:

package xyz.zze.study.freemarker.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import xyz.zze.study.freemarker.bean.Student;

import java.util.*;

@RequestMapping("/freemarker")
@Controller
public class TestController {

    @GetMapping("/test1")
    public String freemarker(Map<String, Object> map) {
        // 向数据模型放数据
        map.put("name", "zze");
        Student stu1 = new Student();
        stu1.setName("小明");
        stu1.setAge(18);
        stu1.setMoney(1000.86f);
        stu1.setBirthday(new Date());
        Student stu2 = new Student();
        stu2.setName("小红");
        stu2.setMoney(200.1f);
        stu2.setAge(19);
        stu2.setBirthday(new Date());
        List<Student> friends = new ArrayList<>();
        friends.add(stu1);
        stu2.setFriends(friends);
        stu2.setBestFriend(stu1);
        List<Student> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);
        //向数据模型放数据
        map.put("stus", stus);
        //准备map数据
        Map<String, Student> stuMap = new HashMap<>();
        stuMap.put("stu1", stu1);
        stuMap.put("stu2", stu2);
        map.put("stu1", stu1);
        map.put("stuMap", stuMap);
        // 返回 src/resources/templates 下的模板名称
        return "test";
    }
}

list 指令

【测试遍历 list】 修改 test.ftl<body> 中的内容为如下:

<table border="1">
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stus as stu>
        <tr>
            <td>${stu_index + 1}</td>
            <td>${stu.name}</td>
            <td>${stu.age}</td>
            <td>${stu.money}</td>
        </tr>
    </#list>
</table>

输出:

image.png

_index:得到循环记录的下标,使用方法是在变量后边加 _index,它的值是从 0 开始。

测试遍历 map】修改 test.ftl<body> 中的内容为如下:

输出 stu1 的学生信息: <br/>
姓名: ${stuMap['stu1'].name}<br/>
年龄: ${stuMap['stu1'].age}<br/>
输出 stu2 的学生信息: <br/>
姓名: ${stuMap.stu2.name}<br/>
年龄: ${stuMap.stu2.age}<br/>
遍历输出两个学生信息: <br/>
<table border="1">
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stuMap?keys as k>
        <tr>
            <td>${k_index + 1}</td>
            <td>${stuMap[k].name}</td>
            <td>${stuMap[k].age}</td>
            <td>${stuMap[k].money}</td>
        </tr>
    </#list>
</table>

输出:

image.png

if 指令

if 指令即判断指令,是常用的 FTL 指令,Freemarker 在解析时遇到 if 会进行判断,条件为真则输出 if 后面代码块中间的内容,否则跳过内容不再输出。
修改 test.ftl<body> 中的内容为如下:

<table border="1">
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stus as stu>
        <tr>
            <td <#if stu_index%2==0>style="background: red" </#if>>${stu_index + 1}</td>
            <td>${stu.name}</td>
            <td>${stu.age}</td>
            <td>${stu.money}</td>
        </tr>
    </#list>
</table>

输出:

image.png

实现的功能是:如果遍历的记录索引是偶数,就给序号单元格背景色设为红色。

运算符

算数运算符

FreeMarker 表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+-*/%

逻辑运算符

逻辑运算符有如下几个:

  • 逻辑与:&&
  • 逻辑或:||
  • 逻辑非:!

逻辑运算符只能作用于布尔值,否则将产生错误

比较运算符

表达式中支持的比较运算符有如下几个:

  • = 或者==:判断两个值是否相等;
  • !=:判断两个值是否不等;
  • > :或者 gt:判断左边值是否大于右边值;
  • >=:或者 gte:判断左边值是否大于等于右边值;
  • < 或者 lt:判断左边值是否小于右边值;
  • <= 或者 lte:判断左边值是否小于等于右边值;’

注意:=!= 可以用于字符串、数值和日期来比较是否相等,但 =!= 两边必须是相同类型的值,否则会产生错误,而且 FreeMarker 是精确比较,xX 是不等的。其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用 gt 等字母运算符代替 > 会有更好的效果,因为 FreeMarker 会把 > 解释成 FTL 标签的结束字符,当然,也可以使用括 号来避免这种情况,如:<#if (x>y)>

空值处理

判断某变量是否存在使用 ??,语法为:变量??,如果该变量存在,返回 true,否则返回 false
例:为当值 stus 为空报错可以加上判断如下:

<#if stus??>
...
</#if>

还可给缺失的变量给定默认值,语法为 变量!默认值 ,当变量为空时则显示默认值。
例:${name!""} 表示如果 name 为空则显示空字符串。
如果是嵌套的对象建议使用 () 括起来。
例:${(stu.bestFriend.name)!""} 表示如果 stubestFriendname 为空,则默认显示空字符串。

内建函数

Freemarker 的内建函数语法格式为 变量?函数名称,常用内建函数如下:

获得集合的大小

集合的大小在 Freemarker 中是可以通过内置函数 size 获取,如下:

${集合变量?size}

日期格式化

  • 显示年月日:${today?date}
  • 显示时分秒:${today?time}
  • 显示日期+时间:${today?datetime}
  • 自定义格式化:${today?string("yyyy-MM-dd")}

这里的 today 代指日期类型的一个变量。

转字符串

字符串的转换需要使用到内置函数 c
例:假如在模板中能取到一个数值类型的变量 num,它的值为 123123113,那么直接使用 ${num} 输出的结果将会是 123,123,113,即每三位使用逗号进行分隔,如果不想显示为每三位分隔的数字,可以使用 c 函数将数字转换为字符串输出,即 $num?c

json 串转对象

还可将 json 字符串转换为对象使用,先看如下示例:

<!-- 模板 -->
<#assign text="{'name':'张三','age':23}" />
<#assign data=text?eval />
${data.name} ${data.age}

对应输出结果为:

张三 23

assgin 的作用是定义一个变量,而 eval 函数的作用就是将 json 字符串转换为一个对象。

静态化

测试资源目录(src/test/resources)下准备模板文件 templates/test.tpl 如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Freemarker</title>
</head>
<body>
Hello ${name}
</body>
</html>

创建测试方法如下:

package xyz.zze.study.freemarker;

import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.jupiter.api.Test;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class Test01 {
    //基于模板文件静态化
    @Test
    public void testGenerateHtmlByTpl() throws IOException, TemplateException {
        //创建配置类
        Configuration configuration = new Configuration(Configuration.getVersion());
        //设置模板路径
        String classpath = this.getClass().getResource("/").getPath();
        configuration.setDirectoryForTemplateLoading(new File(classpath + "/templates/"));
        //设置字符集
        configuration.setDefaultEncoding("utf-8");
        //加载模板
        Template template = configuration.getTemplate("test.ftl");
        //数据模型
        Map<String, Object> map = new HashMap<>();
        map.put("name", "zze");
        //静态化
        String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
        //静态化内容
        System.out.println(content);
    }


    //基于模板字符串静态化
    @Test
    public void  testGenerateHtmlByString() throws IOException, TemplateException {
        //创建配置类
        Configuration configuration = new Configuration(Configuration.getVersion());
        //模板字符串
        String tplStr = "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>Hello Freemarker</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "Hello ${name}\n" +
                "</body>\n" +
                "</html>";
        //设置模板加载器
        StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
        stringTemplateLoader.putTemplate("hello_tpl", tplStr);
        configuration.setTemplateLoader(stringTemplateLoader);
        //得到模板
        Template helloTpl = configuration.getTemplate("hello_tpl","utf-8");
        //数据模型
        Map<String, Object> map = new HashMap<>();
        map.put("name", "zze");
        //静态化
        String content = FreeMarkerTemplateUtils.processTemplateIntoString(helloTpl, map);
        //静态化内容
        System.out.println(content);
    }
}

上述两个测试方法的输出结果如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Freemarker</title>
</head>
<body>
Hello zze
</body>
</html>

将最后输出的字符串保存到 html 文件即完成了静态化。

0

评论区