关于java:Freemarker-教程一模板开发手册

6次阅读

共计 8494 个字符,预计需要花费 22 分钟才能阅读完成。

本文是 Freemarker 系列的第一篇,面向模板开发人员,次要介绍 FreeMarker 所应用的 FTL(FreeMarker Template Language) 语法,理解 Freemarker 的基本概念,介绍根本的 FTL 术语 及内置函数,内置指令,不便作为开发手册速查(文中演示所用版本为 2.3.30,理论应用中请依据本人我的项目版本自查官网)。

本文不会列举官网 API,只在必要时演示其语法,代码工程中有课代表整顿的 freemarker api 思维导图,配合此文食用可使功力大增!请到 课代表的 github 自取。

1.FreeMarker 是什么

Freemarker是一款纯 Java编写的模板引擎软件,能够用来生成各种文本,包含但不限于:HTMLE-Mail以及各种源代码等等。

它的次要工作就是:把模板和数据组装在一起,生成文档,这个过程又叫渲染(Render)。流程如图:

因为大部分模板开发人员都是用它来生成 HTML 页面,所以本文将基于 SpringBoot(2.4.1)+Freemarker(2.3.30)+SpringWeb演示 HTML 页面的渲染

2. 最简略的模板

假如我想要一个简略页面用来欢送以后用户,模板代码:

<html>
<head>
  <title>index</title>
</head>
<body>
    <p> 你好,${userName}</p>
</body>
</html>

${userName}是 FTL 的 插值 语法,他会把 userName 的值替换到生成的 HTML 中,从而依据以后登录者,显示不同的用户名,这个值由后端代码放到 Model 中,对应的 Controlelr 代码:

@Controller
public class HelloWorld {@GetMapping("hello")
    public String hello(Model model) {model.addAttribute("userName","Java 课代表");
        // 返回模板名称
        return "index";
    }
}

拜访页面:


数据由后端代码通过数据模型(Model)传递,模板只关怀数据如何展现(View),二者的关联关系由 Controller 来管制,这就是 MVC。

3. 数据模型(data-model)

Controller 中增加到 model 中的数据是如何组织的呢?这就须要理解一下 FTL 的数据模型(data-model)。

FTL 的数据模型在结构上是一个树形:

(root)
  |
  +- animals
  |   |
  |   +- mouse
  |   |   |
  |   |   +- size = "small"
  |   |   |
  |   |   +- price = 50
  |   |
  |   +- elephant
  |   |   |
  |   |   +- size = "large"
  |   |   |
  |   |   +- price = 5000
  |   |
  |   +- python
  |       |
  |       +- size = "medium"
  |       |
  |       +- price = 4999
  |
  +- message = "It is a test"
  |
  +- misc
      |
      +- foo = "Something"

其中的 root 能够了解为 Controller 中的 model,通过 model.addAttribute("userName","Java 课代表"); 就能够往数据模型中增加数据。

数据模型中能够像目录一样开展的变量,如:root, animals, mouse, elephant, python, misc称之为 哈希 (hash)。哈希的 key 就是变量名,value 就是变量存储的值,通过. 分隔的门路能够拜访变量值,比方拜访 mouse 的 price :animals.mouse.price.

animals.mouse.price 这样存储单个值的变量叫做 标量(scalar),标量有四种具体类型:string,boolean,date-like,number;

还有一种变量叫做 序列(sequence), 能够类比为 Java 中的数组,序列中的每个项没有名字,能够通过遍历,或者下标的形式拜访(前面会演示序列的拜访),它的数据结构看起来是这样的:

(root)
  |
  +- animals
  |   |
  |   +- (1st)
  |   |   |
  |   |   +- name = "mouse"
  |   |   |
  |   |   +- size = "small"
  |   |   |
  |   |   +- price = 50
  |   |
  |   +- (2nd)
  |   |   |
  |   |   +- name = "elephant"
  |   |   |
  |   |   +- size = "large"
  |   |   |
  |   |   +- price = 5000
  |   |
  |   +- (3rd)
  |       |
  |       +- name = "python"
  |       |
  |       +- size = "medium"
  |       |
  |       +- price = 4999
  |
  +- misc
      |
      +- fruits
          |
          +- (1st) = "orange"
          |
          +- (2nd) = "banana"

FTL 里 罕用的数据类型 就这三类:哈希(hashe), 标量(scalar),序列(sequence)。

有了数据,还要有语法来组织这些数据,上面介绍 FTL 中的罕用语法。

4.FTL 语法

FreeMarker 只认如下三种语法:

  1. 插值:${…},Freemarker 会将外面的变量替换为理论值
  2. FTL 标签 (tags):构造上相似 HTML 的标签,都是用<> 包裹起来,一般标签以 <# 结尾,用户自定义标签以 <@ 结尾,如<#if true>true thing<#/if><@myDirect></@myDirect>

你会看到两种叫法,1:标签(tags),2:指令(directive)。举个例子:<#if></#if>叫标签;标签外面的 if 是指令,能够类比于 html 中的标签(如:<table></table>)和元素(如:table)。不过,把标签和指令认为是 同义词 也没有问题。

  1. 正文(Comments):FTL 中的正文是这样的:<#-- 被正文掉的内容 -->,对于正文,FTL 会主动跳过,所以不会显示在生成的文本中(这点有别于 HTML 的正文)

留神:除以上三种语法之外的所有内容,皆被 FreeMarker 视为一般文本,一般文本会被原样输入

插值就是单纯的替换变量的值,正文更没啥好说的,上面次要介绍几个 最罕用 的 FTL 标签(指令)并联合代码演示其用法。

if 指令

if 能够依据条件跳过模板中的某块代码,以前文为例,当 userName 值为 “Java 课代表 ” 或 zhengxl5566 时,用非凡款式展现,相干模板代码如下:

<p> 你好,<#if userName == "Java 课代表">
        <strong>${userName}</strong>
    <#elseif userName == "zhengxl5566">
        <h1>${userName}</h1>
    <#else>
        ${userName}
    </#if>
</p>

list 指令

list 用来遍历序列,其语法为:

<#list sequence as loopVariable>
    repeatThis
</#list>

比方后盾往 model 里放入一个 allUsers 的汇合

model.addAttribute("allUsers",userService.getAllUser());

能够间接应用下标拜访汇合中的某个元素:${allUsers[0].name}

也能够在模板中间接遍历展现:

<ol>
<#list allUsers as user>
    <li>
        姓名:${user.name},年龄:${user.age}
    </li>
</#list>
</ol>

理论渲染进去的 HTML:

<ol>
  <li>
    姓名:zxl,年龄:18
  </li>
  <li>
    姓名:ls,年龄:19
  </li>
  <li>
    姓名:zs,年龄:16
  </li>
</ol>

留神:假如 allUsers 是空的,渲染进去的页面会是<ol></ol>,如果须要躲避这个状况,能够应用 items 标签

<#list allUsers>
    <ol>
        <#items as user>
            <li>
                姓名:${user.name},年龄:${user.age}
            </li>
        </#items>
    </ol>
</#list>

此时,假如 allUsers 是空的,list 标签中的 html 内容就不会被渲染进去。

include 指令

include 指令能够把一个模板的内容插入到另一个模板中(官网倡议应用 import 代替,参见下文的最佳实际)。
假如咱们每个页面都须要一个 footer,能够写一个公共的 footer.ftlh 模板,其余须要 footer 的页面只须要援用 footer.ftlh 模板即可:

<#include "footer.ftlh">

import 指令

import 能够将模板中定义的变量引入以后模板,并在以后模板中应用。它和 include 的次要区别就是 import 能够将变量封装到新的命名空间中(后文会介绍 import 和 include 的比照)。

例如:模板 /libs/commons.ftl 外面写了很多公共办法,想在其余模板里援用,只须要在其余模板的结尾写上:

<#import "/libs/commons.ftl" as com>

后续想应用 /libs/commons.ftl 中的 copyright 办法,能够间接应用:

<@com.copyright date="1999-2002"/>

assign 指令

assign 能够用来创立新的变量并为其赋值,语法如下:

<#assign name1=value1 name2=value2 ... nameN=valueN>
or
<#assign name1=value1 name2=value2 ... nameN=valueN in namespacehash>
or
<#assign name>
  capture this
</#assign>
or
<#assign name in namespacehash>
  capture this
</#assign>

举例:

<#-- 创立字符串 -->
<#assign myStr = "Java 课代表">
<#-- 应用插值语法显示字符串 -->
myStr:${myStr}

macro 指令

macro 用来从模板上创立用户自定义指令(Java 后端能够通过实现 TemplateDirectiveModel 接口自定义指令,将在下一篇:《Freemarker 教程(二)- 后端开发指南》中介绍)

macro 创立的也是变量,该变量能够做为用户自定义指令应用,比方上面的模板定义了 greet指令:

<#macro greet>
  <h1>hello 课代表 </h1>
</#macro>

应用 greet 指令

<@greet></@greet>
或者
<@greet/>

指令还能够附带参数:

<#macro greet person>
  <h1>hello ${person}</h1>
</#macro>

应用时传入 person 变量:

<@greet person="Java 课代表"/> and <@greet person="zhengxl5566"/>

5. 内置函数(Built-ins)

所谓内置函数,就是 FreeMarker 针对不同数 据类型为咱们提供的一些内置办法,有了这些办法,能够让数据在模板中的展现更加不便。应用内置函数时,只须要在变量前面应用 ? 加相应函数名即可。篇幅无限,这里不打算列举所有内置函数,只挑几个简略例子展现其语法。

例子一:字符串的内置函数

<#-- 创立字符串 -->
<#assign myStr = "Java 课代表">
<#-- 首字母小写 -->
${myStr?uncap_first}
<#-- 保留指定字符前面的字符串 -->
${myStr?keep_after("Java")}
<#-- 替换指定字符 -->
${myStr?replace("Java","Freemarker")}

例子二:工夫类型内置函数

<#-- 获取以后工夫(如果是后端将工夫传入 data-model,只须要传 Date 类型即可)-->
<#assign currentDateTime = .now>
<#-- 展现日期局部 -->
${currentDateTime?date}<br>
<#-- 展现工夫局部 -->
${currentDateTime?time}<br>
<#-- 展现日期和工夫局部 -->
${currentDateTime?datetime}<br>
<#-- 按指定格局展现工夫日期 -->
${currentDateTime?string("yyyy-MM-dd HH:mm a")}<br>

例子三:序列的内置函数

<#-- 序列类型内置函数样例 -->
<#assign mySequence = ["Java 课代表","张三","李四","王五"]>
<#-- 将所有元素以指定分隔符宰割,输入字符串 -->
${mySequence?join(",")}<br>
<#-- 取序列中的第一个元素 -->
${mySequence?first}<br>
<#-- 将序列排序后应用逗号宰割输入为字符串 -->
${mySequence?sort?join(",")}<br>

通过以上三个例子的简略演示,置信你曾经能把握内置函数的应用技巧了,就是在变量前面用 ?加变量数据类型所反对的函数。FTL 的内置函数极其丰富,官网按数据类型具体列举了各自反对的内置函数及其用法,可自行查看官网的内置函数参考。

为了不便大家疾速查阅相干内置函数 (built-ins) 和指令 (directives),课代表从官网翻译,并应用 xmind 做了个思维导图,每个函数(指令) 都能够点进去查看性能形容和样例,能够极大进步模板开发效率:

原始 xmind 文件放在 课代表的 github 上,读者能够按需自取。

课代表划重点!这个思维导图是全文精髓,肯定要下载下来看看!

6. 命名空间(Namespaces)

所谓命名空间,就是在同一个模板里,所有应用 assign,macro,function 指令所创立的变量汇合,它的次要作用就是惟一标识一个变量。

有两种形式能够创立命名空间:

1、同一个模板中的变量在同一个命名空间中。

以如下的 index.ftlh 为例,这外面创立的变量都在一个命名空间下,同名变量的值会相符笼罩

<#assign myName = "Java 课代表">
<#assign myName = "课代表">
<#-- 理论输入的是“课代表”-->
${myName}

2、不同模板中的变量能够通过 import 指令来辨别不同的命名空间变量

模板 A 中想应用模板 B 中的变量,能够应用 import 指令,给引入的模板定义一个新的命名空间,通过 as 前面指定的 key 拜访该新命名空间。

比方模板 lib/example.ftlh中定义了copyright

<#macro copyright date>
  <p>Copyright (C) ${date} Someone. All rights reserved.</p>
</#macro>

<#assign mail = "user@example.com">

在另一个模板 index.ftlh 中应用copyright

<#import "lib/example.ftlh" as e>
<@e.copyright date="1999-2002"/>
${e.mail}

命名空间的生命周期(The life-cycle of namespaces)

命名空间由 import 指令中的 path 确定(绝对路径),如果雷同的 path 引入了屡次,只有第一次调用 import 的时候才会触发相应命名空间的创立。前面对于雷同模板门路的 import,指代的都是同一个命名空间,举例:

<#import "/lib/example.ftl" as e>
<#import "/lib/example.ftl" as e2>
<#import "/lib/example.ftl" as e3>
${e.mail}, ${e2.mail}, ${e3.mail}
<#assign mail="other@example.com" in e>
${e.mail}, ${e2.mail}, ${e3.mail}

/lib/example.ftl import 后赋给 e,e2,e3 三个命名空间,批改 e 中的 mail,对 e2,e3 同样失效

输入:

user@example.com, user@example.com, user@example.com
other@example.com, other@example.com, other@example.com

import 和 include 的区别

<#import "lib/example.ftlh" as e> 会创立一个全新的命名空间,并把 lib/example.ftlh 中定义的变量封装到新命名空间中,提供拜访,如:<@e.copyright date="1999-2002"/>

<#include "lib/example.ftlh"> 只是单纯把 example.ftlh 的内容插入到以后模板,并不会对命名空间产生影响

Freemarker 官网倡议:

所有应用 include 的中央都应该被 import 代替

应用 import 益处如下:

  • import 引入的模板只会被执行一次,反复援用屡次并不会反复执行模板。而对于 include 而言,每次 include 都会执行一次模板内容;
  • import 能够创立该模板的命名空间,援用变量时能够清晰表白出变量起源,缩小命名抵触概率;
  • 如果定义了很多通用办法,能够将 auto-import 配置为懒加载,用到哪个加载哪个。而 auto-include 无奈实现懒加载,必须全量加载;
  • import 指令不会有任何输入,而 include 可能会依据模板内容输入相应字符。

7. 最佳实际

1. 空值解决

变量空值的解决

对于不存在的变量和值为 null 的变量,freemarker 对立认为是不存在的值,对其调用将会报错。为了防止这种状况,能够设置默认值,示例:<h1>Welcome ${user!"visitor"}!</h1>,当 user 不存在时,将显示 visitor 字符串。

另一种形式是在变量前面应用 ??表达式,如:user??如果 user 存在,则返回 true,否则返回 false,示例:<#if user??><h1>Welcome ${user}!</h1></#if>,当 user 不存在时,就不展现欢送标语了。

list 中的空值解决

遍历序列的时候,假如序列中有空值,freemarker 并不会间接报错或显示空值,而是像更上一级作用域去搜寻同名变量值,这种行为可能会导致谬误的输入,举例:

<#assign x = 20>
<#list xs as x>
  ${x!'Missing'}
</#list>

本意是当遇到序列 xs 中的空元素是显示“missing”字符串,但因为 freemarker 向上查找的个性,这里的空值将会显示为 20。要敞开这个个性,能够在服务端配置:configuration.setFallbackOnNullLoopVariable(false);

2. 应用 import 代替 include

前文在介绍到 include 的时候提到过,官网倡议应该将所有用到 include 的中央都用 import 实现

咱们平时用到 include 指令,次要就是用来把一段内容插入到以后模板,那如何用 import 实现 include 的性能呢?

很简略,把须要插入的内容封装成 自定义指令就好了。

比方咱们从 common.ftlh 里定义一个自定义指令 myFooter

<#macro myFooter>
    <hr>
    <p> 这里是 footer</p>
</#macro>

在须要应用的中央,引入 common.ftlh 并调用 myFooter 指令

<#import "lib/common.ftlh" as common>
<#-- 将 include 应用 import 代替 -->
<#--<#include "footer.ftlh">-->
<@common.myFooter/>

3. 隔行变色

数据展现的时候常常遇到应用表格展现数据的状况,为了减少辨识度,个别会让奇数行和偶数行色彩不同以辨别,这就是隔行变色

上面以遍历序列为例:

<#assign mySequence = ["Java 课代表","张三","李四","王五"]>
<#list mySequence as name>
    <span style="color:  ${name?item_cycle("red","blue")}">   ${name}<br/> </span>
</#list>
    

这里的 item_cycle 是循环变量的内置函数,至于他的具体用法,再次举荐你去看一下课代表整顿的思维导图。

8. 总结

本文介绍了 Freemarker 的基本概念和根底语法,意在让刚接触的萌新能对 Freemarker 有个全局性意识,理解 Freemarker 的数据模型、内置函数、指令。只有能辨别这几个概念,理论开发中现用现查即可,不要一开始就迷失在海量的 API 中。

总体来说,Freemarker 是一个比较简单,容易上手的模板引擎,只有把握了本文所提及的基本概念,间接上手开发是齐全没问题的。

在本文写作过程中,课代表意识到纯文字表白的局限性,又不能全文列举和翻译 API,于是整顿了一个思维导图,将 Freemarker 的各类指令,内置函数及其官网示例全都翻译整顿了进去。在平时开发过程中极大晋升了开发效率,须要的同学请到 课代表的 github 自取。


???? 关注 Java 课代表,获取最新 Java 干货????

正文完
 0