本文是Freemarker系列的第一篇,面向模板開發人員,主要介紹 FreeMarker 所使用的 FTL(FreeMarker Template Language) 語法,瞭解 Freemarker 的基本概念,介紹基本的 FTL 術語 及內置函數,內置指令,方便做爲開發手冊速查(文中演示所用版本爲 2.3.30,實際使用中請根據本身項目版本自查官網)。html
本文不會羅列官網API,只在必要時演示其語法,代碼工程中有課表明整理的 freemarker api 思惟導圖,配合此文食用可以使功力大增!請到 課表明的 github自取。java
Freemarker
是一款純 Java
編寫的模板引擎軟件,能夠用來生成各類文本,包括但不限於:HTML
,E-Mail
以及各類源代碼等等。python
它的主要任務就是:把模板和數據組裝在一塊兒,生成文檔,這個過程又叫渲染(Render)。流程如圖:git
因爲大部分模板開發人員都是用它來生成HTML頁面,因此本文將基於 SpringBoot(2.4.1)+Freemarker(2.3.30)+SpringWeb
演示 HTML 頁面的渲染github
假設我想要一個簡單頁面用來歡迎當前用戶,模板代碼:spring
<html> <head> <title>index</title> </head> <body> <p>你好,${userName}</p> </body> </html>
${userName}
是 FTL 的插值語法,他會把userName
的值替換到生成的 HTML
中,從而根據當前登陸者,顯示不一樣的用戶名,這個值由後端代碼放到Model中,對應的 Controlelr 代碼:apache
@Controller public class HelloWorld { @GetMapping("hello") public String hello(Model model) { model.addAttribute("userName","Java 課表明"); // 返回模板名稱 return "index"; } }
訪問頁面:json
數據由後端代碼經過數據模型(Model)傳遞,模板只關心數據如何展現(View),兩者的關聯關係由 Controller 來控制,這就是 MVC。segmentfault
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 中的經常使用語法。
FreeMarker 只認以下三種語法:
<>
包裹起來,普通標籤以<#
開頭,用戶自定義標籤以<@
開頭,如<#if true>true thing<#/if>
,<@myDirect></@myDirect>
你會看到兩種叫法,1:標籤(tags),2:指令(directive)。舉個例子:<#if></#if>
叫標籤; 標籤裏面的if
是指令,能夠類比於html中的標籤(如:<table></table>
)和元素(如:table
)。不過,把標籤和指令認爲是 同義詞也沒有問題。
<#-- 被註釋掉的內容 -->
,對於註釋,FTL會自動跳過,因此不會顯示在生成的文本中(這點有別於 HTML 的註釋)注意:除以上三種語法以外的全部內容,皆被 FreeMarker 視爲普通文本,普通文本會被原樣輸出
插值就是單純的替換變量的值,註釋更沒啥好說的,下面主要介紹幾個最經常使用的 FTL 標籤(指令)並結合代碼演示其用法。
if 能夠根據條件跳過模板中的某塊代碼,之前文爲例,當 userName
值爲 "Java課表明" 或zhengxl5566
時,用特殊樣式展現,相關模板代碼以下:
<p>你好, <#if userName == "Java 課表明"> <strong>${userName}</strong> <#elseif userName == "zhengxl5566"> <h1>${userName}</h1> <#else> ${userName} </#if> </p>
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 指令能夠把一個模板的內容插入到另外一個模板中(官方建議使用 import 代替,參見下文的最佳實踐)。
假設咱們每一個頁面都須要一個 footer,能夠寫一個公共的footer.ftlh模板,其他須要footer的頁面只須要引用footer.ftlh模板便可:
<#include "footer.ftlh">
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 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 用來從模板上建立用戶自定義指令(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"/>
所謂內置函數,就是 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上,讀者能夠按需自取。
課表明劃重點!這個思惟導圖是全文精華,必定要下載下來看看!
所謂命名空間,就是在同一個模板裏,全部使用 assign,macro,function 指令所建立的變量集合,它的主要做用就是惟一標識一個變量。
有兩種方式能夠建立命名空間:
一、同一個模板中的變量在同一個命名空間中。
以以下的index.ftlh
爲例,這裏面建立的變量都在一個命名空間下,同名變量的值會相符覆蓋
<#assign myName = "Java 課表明"> <#assign myName = "課表明"> <#--實際輸出的是「課表明」--> ${myName}
二、不一樣模板中的變量能夠經過 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}
命名空間由 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 "lib/example.ftlh" as e>
會建立一個全新的命名空間,並把lib/example.ftlh
中定義的變量封裝到新命名空間中,提供訪問,如:<@e.copyright date="1999-2002"/>
。
<#include "lib/example.ftlh">
只是單純把example.ftlh
的內容插入到當前模板,並不會對命名空間產生影響
Freemarker官方建議:全部使用 include 的地方都應該被 import 替代
使用 import 好處以下:
auto-import
配置爲懶加載,用到哪一個加載哪一個。而auto-include
沒法實現懶加載,必須全量加載;變量空值的處理
對於不存在的變量和值爲 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);
前文在介紹到 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/>
數據展現的時候常常遇到使用表格展現數據的狀況,爲了增長辨識度,通常會讓奇數行和偶數行顏色不一樣以區分,這就是隔行變色
下面以遍歷序列爲例:
<#assign mySequence = ["Java 課表明","張三","李四","王五"]> <#list mySequence as name> <span style="color: ${name?item_cycle("red","blue")}"> ${name}<br/> </span> </#list>
這裏的 item_cycle 是循環變量的內置函數,至於他的詳細用法,再次推薦你去看一下課表明整理的思惟導圖。
本文介紹了 Freemarker 的基本概念和基礎語法,意在讓剛接觸的萌新能對 Freemarker 有個全局性認識,瞭解Freemarker的數據模型、內置函數、指令。只要能區分這幾個概念,實際開發中現用現查便可,不要一開始就迷失在海量的API中。
整體來講,Freemarker 是一個比較簡單,容易上手的模板引擎,只要掌握了本文所說起的基本概念,直接上手開發是徹底沒問題的。
在本文寫做過程當中,課表明意識到純文字表達的侷限性,又不能全文羅列和翻譯API,因而整理了一個思惟導圖,將Freemarker 的各種指令,內置函數及其官網示例全都翻譯整理了進去。在平時開發過程當中極大提高了開發效率,須要的同窗請到 課表明的 github自取。
👇關注 Java課表明,獲取最新 Java 乾貨👇