# FreeMarker
[TOC]
## 導學
FreeMarker是一款模板引擎:一種基于模板的、用來生成輸出文本的通用工具。對于Java Web開發來說,使用FreeMarker模板,可以將Java代碼從頁面中分離出來。在本節課程中,需要掌握模板引擎原理,掌握常用FreeMarker語法,并嘗試著重構之前的案例。
## FreeMarker初識
**什么是模板引擎**
* 模板引擎的目標是“數據 + 模板 = 結果”
* 模板引擎將數據與展現有效“解耦”

那其實我們的Jsp,就是一個模板引擎,在Jsp中,我們可以使用EL表達式輸出數據。但是,也要注意一下在Java Web領域,前端頁面的展示不僅僅有Jsp這一種實現方案。比如我們可以想這樣一個問題,我們可以在HTML文件中使用EL表達式和JSTL嗎?顯然這是不可以的,這時候如果想要在頁面上更有效更方便的展示數據,就需要其他模板引擎了。
**主流模板引擎**
* Java Server Page(Jsp)
* Freemarker(使用歷史長,較Jsp更輕量,開發速度更快)
* Beetl(使用量少,融合Jsp和Freemarker,上手更容易,開發速率比之前兩個更高)
* Thymeleaf(SpringBoot官方推薦使用模板引擎)
**FreeMarker**
1. Freemarker是免費開源的模板引擎技術
2. Freemarker腳本為Freemarker Template Language(主要工作)
3. Freemarker提供了大量的內建函數來簡化開發

## FreeMarker的下載與使用
FreeMarker官網:[https://freemarker.apache.org](https://freemarker.apache.org/)
**下載**

選擇手動下載


**使用**
創建Java Project,導入Jar包,并完成Jar包加載進項目
1. 創建模板加載和數據設置文件
~~~
public class FreemarkerSample1 {
public static void main(String[] args) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
//1. 加載模板
//創建核心配置對象,每個不同的版本FreeMarker的語法都有所不同
Configuration config = new Configuration(Configuration.VERSION_2_3_30);
//設置加載的目錄,這個空字符串代表當前包(即不指定具體包)。
//在FreemarkerSample1類所在的包中加載指定的ftl文件
config.setClassForTemplateLoading(FreemarkerSample1.class, "");
//得到模板對象
Template t = config.getTemplate("sample1.ftl");
//2. 創建數據
Map<String,Object> data = new HashMap<String,Object>();
data.put("site", "新浪");
data.put("url", "http://www.sina.com");
//3. 產生輸出
//此處指定模板對象規范數據以后,具體輸出位置為控制臺
t.process(data, new OutputStreamWriter(System.out));
}
}
~~~
2. 創建數據模板文件-ftl
~~~ftl
<#-- 采用類似于el表達式的方式去除map中key所對應的值 -->
${site}-${url}
~~~
設想一下,前端工程師編寫ftl腳本交給Java工程師,Java工程師就可以利用腳本模板與數據進行結合,將數據代入到對應的模板中,最后按照指定位置進行輸出就可以了。
## FreeMarker基本語法
### ftl取值
~~~
${屬性名} 取值
${屬性名!默認值}
${屬性名?string()} 格式化輸出
~~~
~~~
public class Computer {
private String sn; //序列號
private String model; //型號
private int state; //狀態 1-在用 2-閑置 3-報廢
private String user; //使用人
private Date dop; //采購日期
private Float price; //購買價格
private Map info; //電腦配置信息
public Computer() {
}
public Computer(String sn, String model, int state, String user, Date dop, Float price, Map info) {
super();
this.sn = sn;
this.model = model;
this.state = state;
this.user = user;
this.dop = dop;
this.price = price;
this.info = info;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public Date getDop() {
return dop;
}
public void setDop(Date dop) {
this.dop = dop;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public Map getInfo() {
return info;
}
public void setInfo(Map info) {
this.info = info;
}
}
~~~
~~~
public class FreemarkerSample1 {
public static void main(String[] args) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
//1. 加載模板
//創建核心配置對象,每個不同的版本FreeMarker的語法都有所不同
Configuration config = new Configuration(Configuration.VERSION_2_3_30);
//設置加載的目錄,這個空字符串代表當前包(即不指定具體包)。
//在FreemarkerSample1類所在的包中加載指定的ftl文件
config.setClassForTemplateLoading(FreemarkerSample1.class, "");
//得到模板對象
Template t = config.getTemplate("sample1.ftl");
//2. 創建數據
Map<String,Object> data = new HashMap<String,Object>();
data.put("site", "新浪");
data.put("url", "http://www.sina.com");
data.put("date", new Date());
data.put("number", 837183.883217);
Map info = new HashMap();
info.put("cpu", "i5");
Computer c1 = new Computer("1234567" , "ThinkPad" , 1 , "李四" , new Date() , 12900f , info);
data.put("computer", c1);
//3. 產生輸出
//此處指定模板對象規范數據以后,具體輸出位置為控制臺
t.process(data, new OutputStreamWriter(System.out));
}
}
~~~
~~~ftl
<#-- Freemarker取值 -->
${site}
${url}
<#-- !設置默認值,防止屬性不存在報錯 -->
${author!"不存在的屬性"}
<#-- ?string格式化輸出 -->
${date?string("yyyy年MM月dd日 HH:mm:ss SSS")}
<#-- 數字輸出的時候 FreeMarker默認會保留3位小數 并且3位一逗號 -->
${number?string("0.00")}
<#-- Freemarker對象輸出 -->
SN:${computer.sn}
型號:${computer.model}
狀態:${computer.state}
用戶:${computer.user}
<#-- 日期對象無法直接輸出 -->
采購時間:${computer.dop?string("yyyy年MM月dd日")}
采購價格:${computer.price?string("0.00")}
配置信息:
-----------
CPU:${computer.info["cpu"]}
內存:${computer.info["memory"]!"無內存信息"}
~~~
### 分支判斷
freeMarker中使用`<if>`,`<switch>`標簽進行分支判斷
~~~
<#-- Freemarker取值 -->
${site}
${url}
<#-- !設置默認值,防止屬性不存在報錯 -->
${author!"不存在的屬性"}
<#-- ?string格式化輸出 -->
${date?string("yyyy年MM月dd日 HH:mm:ss SSS")}
<#-- 數字輸出的時候 FreeMarker默認會保留3位小數 并且3位一逗號 -->
${number?string("0.00")}
<#-- ftl中判斷字符串可以直接使用==,在if標簽中不需要使用${}進行取值 -->
<#if computer.sn == "1234567">
重要設備
</#if>
SN:${computer.sn}
型號:${computer.model}
<#if computer.state == 1>
狀態:正在使用
<#elseif computer.state == 2>
狀態:閑置
<#elseif computer.state == 3>
狀態:已作廢
</#if>
<#switch computer.state>
<#case 1>
狀態:正在使用
<#break>
<#case 2>
狀態:閑置
<#break>
<#case 3>
狀態:已作廢
<#break>
<#default>
狀態:無效狀態
</#switch>
<#-- ??代表判斷對象是否為空,true不為空,false為空 -->
<#if computer.user??>
用戶:${computer.user}
</#if>
采購時間:${computer.dop?string("yyyy年MM月dd日")}
采購價格:${computer.price?string("0.00")}
配置信息:
--------------
CPU:${computer.info["cpu"]}
內存:${computer.info["memory"]!"無內存信息"}
~~~
### 迭代
~~~
public class FreemarkerSample2 {
public static void main(String[] args) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
//1. 加載模板
//創建核心配置對象
Configuration config = new Configuration(Configuration.VERSION_2_3_28);
//設置加載的目錄
config.setClassForTemplateLoading(FreemarkerSample2.class, "");
//得到模板對象
Template t = config.getTemplate("sample2.ftl");
//2. 創建數據
Map<String,Object> data = new HashMap<String,Object>();
List<Computer> computers = new ArrayList();
computers.add(new Computer("1234567" , "ThinkPad X1" , 2 , null , new Date() , 12999f , new HashMap() ));
computers.add(new Computer("1234568" , "HP XXX" , 1 , "張三" , new Date() , 7500f , new HashMap() ));
computers.add(new Computer("1234569" , "DELL XXX" , 3 , "李四" , new Date() , 8500f , new HashMap() ));
computers.add(new Computer("1234570" , "ACER XXX" , 1 , "王五" , new Date() , 6300f , new HashMap() ));
computers.add(new Computer("1234571" , "MSI XXX" , 1 , "趙六" , new Date() , 9300f , new HashMap() ));
data.put("computers", computers);
//LinkedHashMap可以保證數據按存放順序進行提取
Map computerMap = new LinkedHashMap();
for(Computer c : computers) {
computerMap.put(c.getSn(), c);
}
data.put("computer_map", computerMap);
//3. 產生輸出
t.process(data, new OutputStreamWriter(System.out));
}
}
~~~
~~~
<#-- 迭代List -->
<#-- 此處將c作為每次迭代的迭代變量 -->
<#list computers as c>
序號:${c_index + 1} <#-- 迭代變量_index保存了循環的索引,從0開始 -->
SN:${c.sn}
型號:${c.model}
<#switch c.state>
<#case 1>
狀態:使用中
<#break>
<#case 2>
狀態:閑置
<#break>
<#case 3>
狀態:已作廢
<#break>
</#switch>
<#if c.user??>
用戶:${c.user}
</#if>
采購日期:${c.dop?string("yyyy-MM-dd")}
采購價格:${c.price?string("0.00")}
-------------------------------------------
</#list>
<#-- 迭代Map -->
==========================================
<#-- ?keys代表獲取map中所有的key值 -->
<#list computer_map?keys as k >
${k}-${computer_map[k].model}
${computer_map[k].price?string("0.00")}
</#list>
~~~
## FreeMarker內建函數
FreeMarker提供了大量的內建函數,這些內建函數可以簡化我們的開發。

~~~
public class FreemarkerSample3 {
public static void main(String[] args) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
//1. 加載模板
//創建核心配置對象
Configuration config = new Configuration(Configuration.VERSION_2_3_28);
//設置加載的目錄
config.setClassForTemplateLoading(FreemarkerSample3.class, "");
//得到模板對象
Template t = config.getTemplate("sample3.ftl");
//2. 創建數據
Map<String,Object> data = new HashMap<String,Object>();
data.put("name", "jackson");
data.put("brand", "bmw");
data.put("words", "first blood");
data.put("n", 37981.83);
data.put("date", new Date());
List<Computer> computers = new ArrayList();
computers.add(new Computer("1234567" , "ThinkPad X1" , 2 , null , new Date() , 12999f , new HashMap() ));
computers.add(new Computer("1234568" , "HP XXX" , 1 , "張三" , new Date() , 7500f , new HashMap() ));
computers.add(new Computer("1234569" , "DELL XXX" , 3 , "李四" , new Date() , 8500f , new HashMap() ));
computers.add(new Computer("1234570" , "ACER XXX" , 1 , "王五" , new Date() , 6300f , new HashMap() ));
computers.add(new Computer("1234571" , "MSI XXX" , 1 , "趙六" , new Date() , 9300f , new HashMap() ));
data.put("computers", computers);
//3. 產生輸出
t.process(data, new OutputStreamWriter(System.out));
}
}
~~~
~~~
${name?cap_first}
${brand?upper_case}
${brand?length}
${words?replace("blood" , "*****")}
${words?index_of("blood")}
<#-- 利用?string實現三目運算符的操作 -->
${(words?index_of("blood") != -1)?string("包含敏感詞匯","不包含敏感詞匯")}
${n?round}
${n?floor}
${n?ceiling}
公司共有${computers?size}臺電腦
第一臺:${computers?first.model}
最后一臺:${computers?last.model}
<#-- sort_by默認升序排列,利用reverse(反轉)可以實現降序排列 -->
<#list computers?sort_by("price")?reverse as c>
${c.sn}-${c.price}
</#list>
~~~
FreeMarker中文參考手冊網址
http://freemarker.foofun.cn/
## Freemarker與Servlet整合
~~~
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>fm-web</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- 為了使FreeMarker能夠與Web項目相結合,需要對Freemarker的核心類FreemarkerServlet進行配置 -->
<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
<!-- 設置ftl文件的加載地址,限制ftl文件存放位置 -->
<init-param>
<param-name>TemplatePath</param-name>
<param-value>/WEB-INF/ftl</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>freemarker</servlet-name>
<!-- 該路徑指的是如果瀏覽器中輸入的是.ftl結尾的路徑,就會交由FreemarkerServlet進行處理,這也避免了web-inf目錄下資源不可直接通過瀏覽器訪問的機制 -->
<url-pattern>*.ftl</url-pattern>
</servlet-mapping>
</web-app>
~~~
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>員工列表</title>
<link href="css/bootstrap.css" type="text/css" rel="stylesheet"></link>
<script type="text/javascript" src="js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="js/bootstrap.js"></script>
<style type="text/css">
.pagination {
margin: 0px
}
.pagination > li > a, .pagination > li > span {
margin: 0 5px;
border: 1px solid #dddddd;
}
.glyphicon {
margin-right: 3px;
}
.form-control[readonly] {
cursor: pointer;
background-color: white;
}
#dlgPhoto .modal-body{
text-align: center;
}
.preview{
max-width: 500px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<h1 style="text-align: center">dodoke員工信息表</h1>
<div class="panel panel-default">
<div class="clearfix panel-heading ">
<div class="input-group" style="width: 500px;">
</div>
</div>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>序號</th>
<th>員工編號</th>
<th>姓名</th>
<th>部門</th>
<th>職務</th>
<th>工資</th>
<th> </th>
</tr>
</thead>
<tbody>
<#list employee_list as emp>
<tr>
<td>${emp_index + 1}</td>
<td>${emp.empno?string("0")}</td>
<td>${emp.ename}</td>
<td>${emp.department}</td>
<td>${emp.job}</td>
<td style="color: red;font-weight: bold">¥${emp.salary?string("0.00")}</td>
</tr>
</#list>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
~~~
~~~
public class Employee {
private Integer empno;
private String ename;
private String department;
private String job;
private Float salary;
public Employee() {
}
public Employee(Integer empno, String ename, String department, String job, Float salary) {
super();
this.empno = empno;
this.ename = ename;
this.department = department;
this.job = job;
this.salary = salary;
}
public Integer getEmpno() {
return empno;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
}
~~~
~~~
@WebServlet("/list")
public class ListServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public ListServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List list = new ArrayList();
list.add(new Employee(7731,"張三" , "市場部" , "客戶代表" , 8000f));
list.add(new Employee(8871,"李四" , "研發部" , "運維工程師" , 7000f));
request.getServletContext().setAttribute("employee_list", list);
request.getRequestDispatcher("/employee.ftl").forward(request, response);
}
}
~~~
配置FreeMarker后,可以將保存數據的集合或列表放在context、session、request的屬性里,在 . ftl文件里使用時直接寫集合或列表的屬性名,FreeMarker能夠直接找到該數據。
(FreeMarker模板引擎首先會從請求中查找,請求中沒有會話中查找,會話中沒有在去查找ServletContext應用程序全局,3種都沒有才會停止程序執行)