【Spring-Boot-Blog-In-Action】第5章 集成 Thymeleaf 模版引擎

5.一、Thymeleaf

5.1.一、Thymeleaf 簡介

  • 模板引擎:Thymeleaf 是一種用於Web和獨立環境的現代服務器端的 Java 模板引擎。可以處理 HTML,XML,JavaScript,CSS 甚至純文本。相似於 JSP、Freemarker。javascript

  • 天然模板,原型即界面:Thymeleaf創建在天然模板的概念之上,以不影響模板做爲設計原型的方式將其邏輯注入到模板文件中。 這改善了設計溝通,彌合了前端設計和開發人員之間的理解誤差。php

  • 語法優雅易懂:支持 OGNLSpringEL 表達式css

  • 遵循 Web 標準:支持 HTML5html

5.1.二、Thymeleaf 標準方言

一、什麼是標準方言?前端

定義了一組功能的 Thymeleaf 語法。java

例如:包含以th前綴開頭的屬性,如<span th:text="..."><span data-th-text="...">git

Thymeleaf 模板:github

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Good Thymes Virtual Grocery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" media="all" href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
  </head>
  <body>
    <p th:text="#{home.welcome}">Welcome to our grocery store!</p>
  </body>
</html>
複製代碼

在接下來的內容中,咱們會學到關於 Thymeleaf 的一下內容:web

Thymeleaf

二、標準表達式語法spring

Thymeleaf屬性容許將它們的值設置爲或包含表達式,因爲它們使用的方言,咱們將其稱爲標準表達式。這些表達式能夠有五種類型:

  • ${...} : 變量表達式。
  • *{...} : 選擇表達式。
  • #{...} : 消息 (i18n) 表達式。
  • @{...} : 連接 (URL) 表達式。
  • ~{...} : 片斷表達式。

2.一、變量表達式

變量表達式是OGNL表達式 - 若是將ThymeleafSpring - 集成在上下文變量上(也稱爲Spring術語中的模型屬性),則爲Spring EL。 它們看起來像這樣:

${session.user.name}
複製代碼

它們做爲屬性值或做爲它們的一部分,取決於屬性:

<span th:text="${book.author.name}">
複製代碼

2.二、選擇表達式

選擇表達式就像變量表達式同樣,它們不是整個上下文變量映射上執行,而是在先前選擇的對象。 它們看起來像這樣:

*{customer.name}
複製代碼

它們所做用的對象由th:object屬性指定:

<div th:object="${book}">
  ...
  <span th:text="*{title}">...</span>
  ...
</div>
複製代碼

2.三、消息(i18n)表達式

消息表達式(一般稱爲文本外部化,國際化或i18n)容許從外部源(如:.properties)文件中檢索特定於語言環境的消息,經過鍵來引用這引用消息。

在Spring應用程序中,它將自動與Spring的MessageSource機制集成。以下:

#{main.title}
#{message.entrycreated(${entryId})}
複製代碼

如下是在模板中使用它們的方式:

<table>
  ...
  <th th:text="#{header.address.city}">...</th>
  <th th:text="#{header.address.country}">...</th>
  ...
</table>
複製代碼

2.四、連接(URL)表達式

連接表達式在構建URL並向其添加有用的上下文會話信息(一般稱爲URL重寫的過程)。 所以,對於部署在Web服務器的/myapp上下文中的Web應用程序,可使用如下表達式:

<a th:href="@{/order/list}">...</a>
複製代碼

能夠轉換成以下的東西:

<a href="/myapp/order/list">...</a>
複製代碼

甚至,若是須要保持會話,而且cookie未啓用(或者服務器還不知道),那麼生成的格式爲:

<a href="/myapp/order/list;jsessionid=s2ds3fa31abd241e2a01932">...</a>
複製代碼

網址也能夠帶參數,以下所示:

<a th:href="@{/order/details(id=${orderId},type=${orderType})}">...</a>
複製代碼

這將產生相似如下的結果 -

<!-- 注意&符號會在標籤屬性中進行HTML轉義... -->
<a href="/myapp/order/details?id=23&type=online">...</a>
複製代碼

連接表達式能夠是相對的,在這種狀況下,應用程序上下文將不會被加到URL的前面:

<a th:href="@{../documents/report}">...</a>
複製代碼

也是服務器相對的(一樣,沒有應用程序上下文的前綴):

<a th:href="@{~/contents/main}">...</a>
複製代碼

和協議相關(就像絕對URL同樣,但瀏覽器將使用與正在顯示的頁面相同的HTTP或HTTPS協議):

<a th:href="@{//static.mycompany.com/res/initial}">...</a>
複製代碼

固然,連接表達式也能夠是絕對的:

<a th:href="@{http://www.mycompany.com/main}">...</a>
複製代碼

可是絕對(或協議相對)URL ,在 Thymeleaf 連接表達式中應該添加什麼值? 很簡單:由響應過濾器定義URL重寫:在基於Servlet的Web應用程序中,對於每一個輸出的URL(上下文相對,相對,絕對…),在顯示URL以前,Thymeleaf老是調用HttpServletResponse.encodeUrl(...)機制。 這意味着一個過濾器能夠經過包裝HttpServletResponse對象來爲應用程序執行自定義的URL重寫。

2.六、片斷表達式

片斷表達式是一種簡單的方法用來表示標記的片斷並將其移動到模板中。 因爲這些表達式,片斷能夠被複制,傳遞給其餘模板的參數等等。

最多見的是使用th:insertth:replace來插入片斷:

<div th:insert="~{commons :: main}">...</div>
複製代碼

可是它們能夠在任何地方使用,就像任何其餘變量同樣:

<div th:with="frag=~{footer :: #main/text()}">
  <p th:insert="${frag}">
</div>
複製代碼

2.五、 文字和操做

有不少類型的文字和操做可用,它們分別以下:

  • 文字
    • 文本文字,例如:'one text', 'Another one!',
    • 數字文字,例如:0,10, 314, 31.01, 112.83,
    • 布爾文字,例如:true,false
    • Null文字,例如:Null
    • 文字標記,例如:one, sometext, main,
  • 文本操做:
    • 字符串鏈接:+
    • 文字替換:|The name is ${name}|
  • 算術運算:
    • 二進制操做:+, -, *, /, %
    • 減號(一元運算符):-
  • 布爾運算:
    • 二進制運算符,and,or
    • 布爾否認(一元運算符):!,not
  • 比較和相等:
    • 比較運算符:>,<,>=,<=(gt,lt,ge,le)
    • 相等運算符:==, != (eq, ne)
  • 條件操做符:
    • If-then:(if) ? (then)
    • If-then-else:(if) ? (then) : (else)
    • Default: (value) ?: (defaultvalue)

5.1.三、設置屬性值

一、設置任意屬性值:th:att

假設咱們的網站發佈了一個時事通信,咱們但願咱們的用戶可以訂閱它,因此咱們建立一個帶有表單的/WEB-INF/templates/subscribe.html模板:

<form action="subscribe.html">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" />
  </fieldset>
</form>
複製代碼

與 Thymeleaf 同樣,此模板更像是靜態原型,而不是 web application 的模板。首先,咱們表單中的action屬性靜態連接到模板文件自己,所以沒有地方能夠進行有用的 URL 重寫。其次,提交按鈕中的value屬性使其顯示英文文本,但咱們但願它可以國際化。

使用th:attr屬性,以及更改其設置的標記屬性的 value 的能力:

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>
複製代碼

這個概念很是簡單:th:attr只須要一個爲屬性賦予 value 的表達式。建立了相應的控制器和消息 files 後,處理該文件的結果將是:

<form action="/gtvg/subscribe">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="¡Suscríbe!"/>
  </fieldset>
</form>
複製代碼

除了新的屬性值以外,您還能夠看到 applicacion context name 已自動添加到/gtvg/subscribe中的 URL 基礎做爲前綴。

1574089663756

二、將 value 設置爲特定屬性

到如今爲止,你可能會想到如下內容:

<input type="submit" value="Subscribe!" th:attr="value=#{subscribe .submit}"/>
複製代碼

這是一個很是醜陋的標記。在屬性的 value 中指定賦值可能很是實用,但若是你必須在 time 中完成,那麼它不是建立模板的最優雅方式。

Thymeleaf 贊成你的意見,這就是爲何在模板中幾乎不使用th:attr的緣由。一般,您將使用其任務設置特定標記屬性的其餘th:*屬性(而不只僅是th:attr之類的任何屬性)。

對於 example,要設置value屬性,請使用th:value

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
複製代碼

這看起來好多了!讓咱們嘗試對form標籤中的action屬性執行相同的操做:

<form action="subscribe.html" th:action="@{/subscribe}">
複製代碼

簡化以下圖所示:

1574173606463

有不少這樣的屬性,每一個屬性都針對特定的 HTML5 屬性,更多特定屬性能夠參考官方文檔:

th:abbr th:accept th:accept-charset
th:accesskey th:action th:align
th:alt th:archive th:audio
th:autocomplete th:axis th:background
... ... ...

三、固定值布爾屬性Fixed-value boolean 屬性

HTML 具備 boolean 屬性的概念,沒有 value 的屬性和 1 的 precence 意味着 value 是「true」。在 XHTML 中,這些屬性只佔用 1 value,這自己也是如此。

對於 example,checked

<input type="checkbox" name="option2" checked /> <!-- HTML -->
<input type="checkbox" name="option1" checked="checked" /> <!-- XHTML -->
複製代碼

標準方言包含容許您經過評估條件來設置這些屬性的屬性,所以若是計算爲 true,則屬性將設置爲其固定 value,若是計算爲 false,則不會設置該屬性:

<input type="checkbox" name="active" th:checked="${user.active}" />
複製代碼

標準方言中存在如下 fixed-value boolean 屬性,更多屬性能夠參考官方文檔:

th:async th:autofocus th:autoplay
th:checked th:controls th:declare
th:default th:defer th:disabled
th:formnovalidate th:hidden th:ismap
th:loop th:multiple th:novalidate
th:nowrap th:open th:pubdate
th:readonly th:required th:reversed
th:scoped th:seamless th:selected

5.1.四、迭代器

一、標準方言爲咱們提供了一個屬性:th:each 來遍歷屬性值

<tr th:each="prod : ${prods}">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
複製代碼

在上面看到的prod : ${prods}屬性 value 意味着「對於評估${prods}的結果中的每一個元素,使用名爲 prod 的變量中的當前元素重複此模板片斷。

  • 咱們將${prods}稱爲迭表明達式或迭代變量。
  • 咱們將prod稱爲迭代變量或簡稱爲變量。

二、當使用th:each時,Thymeleaf 提供了一種用於跟蹤迭代狀態的機制:狀態變量。

狀態變量在th:each屬性中定義,幷包含如下數據:

  • 當前迭代索引,從 0 開始。這是index property。
  • 當前迭代索引,從 1 開始。這是count property。
  • 迭代變量中元素的總量。這是size property。
  • 每次迭代的 iter 變量。這是current property。
  • 當前迭代是偶數仍是奇數。這些是even/odd boolean properties。
  • 當前迭代是不是第一個。這是first boolean property。
  • 當前迭代是不是最後一次。這是last boolean property。

具體使用以下圖中的例子所示:

1574174649832

5.1.五、條件語句

一、簡單條件:「if」和「除非」

th:if屬性:

1574174856433

此外,th:if有一個逆屬性th:unless

<a href="comments.html" th:href="@{/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a>
複製代碼

二、切換語句

還有一種方法可使用 Java 中的等效開關結構有條件地顯示內容:th:switch/th:case屬性集。

只要有一個th:case屬性計算爲true,同一個 switch context 中的其餘th:case屬性都將被計算爲false。默認選項指定爲th:case="*"

<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>
複製代碼

5.1.六、模板佈局

一、定義和引用片斷

在咱們的模板中,咱們一般但願包含來自其餘模板的部分,例如頁腳,頁眉,菜單......

爲了作到這一點,Thymeleaf 須要咱們定義這些部分,「片斷」,以便包含,這可使用th:fragment屬性來完成。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <body>
    <div th:fragment="copy">
      &copy; 2011 The Good Thymes Virtual Grocery
    </div>
  </body>
</html>
複製代碼

上面的 code 定義了一個名爲copy的片斷,咱們可使用th:insertth:replace屬性中的一個輕鬆地在咱們的主頁中包含它(以及th:include,可是自 Thymeleaf 3.0 以來再也不推薦使用它):

<body>
  ...
  <div th:insert="~{footer :: copy}"></div>
</body>
複製代碼

請注意,th:insert須要一個片斷表達式(~{...}),它是一個致使片斷的表達式。在上面的 example 中,這是一個 non-complex 片斷表達式,(~{})封閉是徹底可選的,因此上面的 code 等同於:

<body>
  ...
  <div th:insert="footer :: copy"></div> 
</body>
複製代碼

二、在沒有 th:fragment 的狀況下引用片斷

因爲 Markup Selector 的強大功能,咱們能夠包含不使用任何th:fragment屬性的片斷。它甚至能夠是來自不一樣的 application 的標記 code,徹底不瞭解 Thymeleaf:

...
<div id="copy-section">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>
...
複製代碼

咱們可使用上面的片斷簡單地經過其id屬性引用它,方式與 CSS 選擇器相似:

<body>
  ...
  <div th:insert="~{footer :: #copy-section}"></div> 
</body>
複製代碼

三、th:insert 、 th:replace 和 th:include之間的區別

th:insertth:replaceth:include(自 3.0 以來不推薦)之間有什麼區別:

  • th:insert是最簡單的:它只是將指定的片斷做爲其 host 標記的主體插入。
  • th:replace實際上用指定的片斷替換了它的 host 標記。
  • th:include相似於th:insert,但它不是插入片斷,而是僅插入此片斷的內容。

舉例:

定義一個像這樣的 HTML 片斷:

<footer th:fragment="copy">
  &copy; 2011 The Good Thymes Virtual Grocery
</footer>
複製代碼

使用th:insert 、 th:replace、 th:include中進行片斷引用,以下所示:

<body>
  ...
  <div th:insert="footer :: copy"></div>

  <div th:replace="footer :: copy"></div>

  <div th:include="footer :: copy"></div>
  
</body>
複製代碼

最終效果:

<body>
  ...
  <div>
    <footer>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>

  <div>
    &copy; 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>
複製代碼

更多內容讀者能夠自行參考官文檔,這裏只作簡單入門介紹。

5.1.七、屬性優先

在同一個標籤中寫入多個th:*屬性會發生什麼?例如:

<ul>
  <li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>
</ul>
複製代碼

咱們但願在th:text以前執行th:each屬性,以便咱們獲得咱們想要的結果,可是考慮到 HTML/XML 標準沒有給寫入標籤中的屬性的 order 賦予任何意義,優先級必須在 order 中的屬性自己中創建機制,以確保它將按預期工做。

所以,全部 Thymeleaf 屬性都定義了一個數字優先級,它創建了在標記中執行它們的順序。這個順序是:

優先級 特徵 屬性
1 片斷包含 th:insert th:replace
2 片斷迭代 th:each
3 有條件的 evaluation th:if th:unless th:switch th:case
4 局部變量定義 th:object th:with
5 通常屬性修改 th:attr th:attrprepend th:attrappend
6 具體屬性修改 th:value th:href th:src ...
7 文字(標籤正文修改) th:text th:utext
8 片斷規範 th:fragment
9 片斷刪除 th:remove

5.1.八、Comments(註釋)

一、標準 HTML/XML comments

標準 HTML/XML comments <!-- ... -->能夠在 Thymeleaf 模板中的任何位置使用。這些 comments 中的任何內容都不會由 Thymeleaf 處理,並將逐字複製到結果中:

<!-- User info follows -->
<div th:text="${...}">
  ...
</div>
複製代碼

二、Thymeleaf 解析器級註釋塊

Thymeleaf 將刪除<!--/**/-->之間的全部內容,所以當模板靜態打開時,這些 comment 塊也可用於顯示 code,知道在 Thymeleaf 處理它時它將被刪除:

<!--/*--> 
  <div>
     you can see me only before Thymeleaf processes me!
  </div>
<!--*/-->
複製代碼

三、原型註釋塊

Thymeleaf prototype-only :當模板靜態打開(好比做爲原型)時,Thymeleaf 容許定義標記爲 原型註釋的特殊註釋塊,但在執行模板時 Thymeleaf 認爲是正常標記。

<span>hello!</span>
<!--/*/ <div th:text="${...}"> ... </div> /*/-->
<span>goodbye!</span>
複製代碼

Thymeleaf 的解析系統將簡單地刪除<!--/*//*/-->標記,但不刪除其內容,所以將取消註釋。所以,在執行模板時,Thymeleaf 實際上會看到:

<span>hello!</span>
 
  <div th:text="${...}">
    ...
  </div>
 
<span>goodbye!</span>
複製代碼

5.1.九、內聯

一、表達內聯式

[[...]][(...)]之間的表達式被稱爲是 Thymeleaf 中的內聯表達式,在其中咱們可使用任何在th:textth:utext屬性中也有效的表達式。

雖然標準方言容許咱們使用標記屬性來完成幾乎全部操做,但在某些狀況下咱們可能更喜歡將表達式直接編寫到 HTML 文本中。例如,咱們更喜歡寫這個:

<p>Hello, [[${session.user.name}]]!</p>
複製代碼

而不是這個:

<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>
複製代碼

請注意,雖然[[...]]對應於th:text(i.e.結果將是 HTML 轉義),但[(...)]對應於th:utext而且不會執行任何 HTML轉義。因此對於一個變量如msg = 'This is <b>great!</b>',給定這個片斷:

<p>The message is "[(${msg})]"</p>
複製代碼

結果將使那些<b>標籤未轉義,所以:

<p>The message is "This is <b>great!</b>"</p>
複製代碼

而若是像如下同樣轉義:

<p>The message is "[[${msg}]]"</p>
複製代碼

結果將是 HTML 轉義後的效果:

<p>The message is "This is &lt;b&gt;great!&lt;/b&gt;"</p>
複製代碼

二、禁用內聯

咱們也能夠禁用此機制,由於實際上可能存在咱們想要輸出[[...]][(...)] 內容文本而不將其內容做爲表達式處理的狀況。爲此,咱們將使用th:inline="none"

<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>
複製代碼

這將輸出:

<p>A double array looks like this: [[1, 2, 3], [4, 5]]!</p>
複製代碼

三、JavaScript 內聯

JavaScript 內聯容許在HTML模板模式下處理的模板中更好地整合 JavaScript <script>塊。

與文本內聯同樣,這實際上至關於處理腳本內容,就好像它們是JAVASCRIPT模板模式中的模板同樣,所以文本模板模式的全部功能都將在眼前。可是,在本節中,咱們將重點介紹如何使用它將 Thymeleaf 表達式的輸出添加到 JavaScript 塊中。

必須使用th:inline="javascript"顯式啓用此模式:

<script th:inline="javascript"> ... var username = [[${session.user.name}]]; ... </script>
複製代碼

這將致使:

<script th:inline="javascript"> ... var username = "Sebastian \"Fruity\" Applejuice"; ... </script>
複製代碼

四、CSS 內聯

Thymeleaf 還容許在 CSS <style>標籤中使用內聯,例如:

<style th:inline="css"> ... </style>
複製代碼

對於 example,假設咱們將兩個變量設置爲兩個不一樣的String值:

classname = 'main elems'
align = 'center'
複製代碼

咱們能夠像如下同樣使用它們:

<style th:inline="css"> .[[${classname}]] { text-align: [[${align}]]; } </style>
複製代碼

執行結果將是:

<style th:inline="css"> .main\ elems { text-align: center; } </style>
複製代碼

5.1.十、表達式基本對象 Expression Basic Objects

一些 objects 和變量 maps 始終能夠調用。

一、基礎 objects

  • #ctx:上下文對象 context object。是 org.thymeleaf.context.IContextorg.thymeleaf.context.IWebContext的實現。
/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.context.IContext
 * ======================================================================
 */

${#ctx.locale}
${#ctx.variableNames}

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.context.IWebContext
 * ======================================================================
 */

${#ctx.request}
${#ctx.response}
${#ctx.session}
${#ctx.servletContext}
複製代碼

注意#vars#root是同一 object 的同義詞,但建議使用#ctx

  • # locale:直接訪問與當前請求關聯的java.util.Locale
${#locale}
複製代碼

二、命名空間 request/session 屬性

在 web 環境中使用 Thymeleaf 時,咱們可使用一系列快捷方式來訪問請求參數,session 屬性和 application 屬性:

請注意,這些不是 context objects,但 maps 做爲變量添加到 context,所以咱們在沒有#的狀況下訪問它們。在某種程度上,它們充當命名空間。

  • param:用於檢索請求參數。 ${param.foo}是帶有foo請求參數值的String[],所以${param.foo[0]}一般用於獲取第一個 value。
/* * ============================================================================ * See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap * ============================================================================ */

${param.foo}              // Retrieves a String[] with the values of request parameter 'foo'
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}
...
複製代碼
  • session:用於檢索 session 屬性。
/* * ====================================================================== * See javadoc API for class org.thymeleaf.context.WebSessionVariablesMap * ====================================================================== */

${session.foo}                 // Retrieves the session atttribute 'foo'
${session.size()}
${session.isEmpty()}
${session.containsKey('foo')}
...
複製代碼
  • application:用於檢索 application/servlet context 屬性
/* * ============================================================================= * See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap * ============================================================================= */

${application.foo}              // Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}
...
複製代碼

三、Web上下文對象 Web context objects

在 web 環境中,還能夠直接訪問如下 objects(注意這些是 objects,而不是 maps/namespaces):

  • #request:直接訪問與當前請求關聯的javax.servlet.http.HttpServletRequest object。
${#request.getAttribute('foo')}
${#request.getParameter('foo')}
${#request.getContextPath()}
${#request.getRequestName()}
...
複製代碼
  • # session:直接訪問與當前請求關聯的javax.servlet.http.HttpSession object。
${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}
...
複製代碼
  • #servletContext:直接訪問與當前請求關聯的javax.servlet.ServletContext object。
${#servletContext.getAttribute('foo')}
${#servletContext.contextPath}
...
複製代碼

至此,咱們關於 Thymeleaf 的入門介紹就結束了,更多的相關內容能夠參考相關文檔:

5.二、Thymeleaf 與 Spring Boot 集成

上一節咱們快速介紹了 Thymeleaf 的一些語法和理論知識,這一節咱們來進入實戰,學習一下Thymeleaf 如何與 Spring Boot 集成。

新建一個項目 thymeleaf-in-action ,引入lombok 和 web 依賴:

thymeleaf-in-action

將以前 hello-world 項目中的 controller 拷貝過來,引入 thymeleaf 依賴:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>
複製代碼

完整 pom 文件以下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springboot.blog</groupId>
    <artifactId>thymeleaf-in-action</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>thymeleaf-in-action</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
複製代碼

至此,咱們的 thymeleaf 集成就完成了,啓動項目在瀏覽器中訪問 http://localhost:8080/hello 能夠看到返回 Hello World!

5.三、Thymeleaf 實戰

上一節咱們在 Spring Boot 中成功集成了 Thymeleaf ,這一節咱們繼續在 thymeleaf-in-action 項目中來學習和完善。

首先咱們修改 application.properties 文件:

# THYMELEAF
spring.thymeleaf.encoding=UTF-8
# 熱部署靜態文件
spring.thymeleaf.cache=false
# 使用HTML5標準,這個若是提示 warring 能夠改用 HTML 標準
spring.thymeleaf.mode=HTML5
複製代碼

5.3一、設計API

  • GET /users : 返回用於展示用戶列表的 list.heml 頁面
  • GET /users/{id} :返回用於展示用戶信息的 view.html 頁面
  • GET /users/form :返回用於新增或修改用戶的 form.html 頁面
  • POST /users:新增或修改用戶,成功後重定向到 list.html 頁面
  • GET /users/delete/{id} :根據 id 刪除用戶數據,成功後重定向到 list.html 頁面
  • GET /users/modify/{id} :根據 id 獲取用戶數據,並返回 form.html 頁面用來執行修改

此處只作演示,未使用遵循 rest 風格的接口

5.3.二、後臺編碼

  • 實體 User
  • 資源庫 UserRepository :用戶存儲操做
  • 控制器 UserController :處理用戶請求

一、在src\main\java\com\springboot\blog\domain\包下新建 User.java

1574295231366

User 代碼以下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    /** 用戶的惟一標識 */
    private Long id;
    private String name;
    private Integer age;
}
複製代碼

二、在 src\main\java\com\springboot\blog\repository\ 包下新建 UserRepository.java接口:

public interface UserRepository {

    /** * 新增或者修改用戶 * * @param user * @return */
    User saveOrUpdateUser(User user);

    /** * 刪除用戶 * * @param id */
    void deleteUser(Long id);

    /** * 根據用戶id獲取用戶 * * @param id * @return */
    User getUserById(Long id);

    /** * 獲取全部用戶的列表 * * @return */
    List<User> listUser();
}
複製代碼

src\main\java\com\springboot\blog\repository\impl\ 包下新建 UserRepositoryImpl.java 類來實現 UserRepository 接口。

由於咱們如今尚未使用數據庫,因此咱們能夠先將用戶的數據存儲在一個 MAP 中,咱們這裏使用ConcurrentHashMap 來存儲。

由於用戶 Id 惟一,咱們可使用 AtomicLong 來每一次遞增一個數生成用戶 id。

@Repository // 別忘了注入bean
public class UserRepositoryImpl implements UserRepository {

    /** * 由於用戶 Id 惟一,咱們可使用 AtomicLong 來每一次遞增一個數生成用戶 id。 */
    private static AtomicLong counter = new AtomicLong();
    /** * 由於咱們如今尚未使用數據庫,因此咱們能夠先將用戶的數據存儲在一個 MAP中,咱們這裏使用ConcurrentHashMap來存儲。 */
    private final ConcurrentMap<Long, User> userMap = new ConcurrentHashMap<>();

    @Override
    public User saveOrUpdateUser(User user) {
        Long id = user.getId();
        // 新建
        if (id == null) {
            // id自增
            id = counter.incrementAndGet();
            user.setId(id);
        }
        this.userMap.put(id, user);
        return user;
    }

    @Override
    public void deleteUser(Long id) {
        this.userMap.remove(id);
    }

    @Override
    public User getUserById(Long id) {
        return this.userMap.get(id);
    }

    @Override
    public List<User> listUser() {
        return (ArrayList<User>) this.userMap.values();
    }
}
複製代碼

三、在 src\main\java\com\springboot\blog\controller\ 包下新建 UserController.java 類,並實現以前設計的API接口。

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    /** * 從 用戶存儲庫 獲取用戶列表 * * @return */
    private List<User> getUserList() {
        return userRepository.listUser();
    }

    /** * 查詢所用用戶 * * @param model * @return */
    @GetMapping
    public ModelAndView list(Model model) {
        model.addAttribute("userList", this.getUserList());
        model.addAttribute("title", "用戶管理");
        return new ModelAndView("users/list", "userModel", model);
    }

    /** * 根據id查詢用戶 * * @param id * @param model * @return */
    @GetMapping("{id}")
    public ModelAndView view(@PathVariable("id") Long id, Model model) {
        User user = userRepository.getUserById(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "查看用戶");
        return new ModelAndView("users/view", "userModel", model);
    }

    /** * 獲取 form 表單頁面 * * @param model * @return */
    @GetMapping("/form")
    public ModelAndView createForm(Model model) {
        model.addAttribute("user", new User());
        model.addAttribute("title", "建立用戶");
        return new ModelAndView("users/form", "userModel", model);
    }

    /** * 新建用戶 * * @param user * @return */
    @PostMapping
    public ModelAndView create(User user) {
        user = userRepository.saveOrUpdateUser(user);
        return new ModelAndView("redirect:/users");
    }

    /** * 刪除用戶 * * @param id * @param model * @return */
    @GetMapping(value = "delete/{id}")
    public ModelAndView delete(@PathVariable("id") Long id, Model model) {
        userRepository.deleteUser(id);
        model.addAttribute("userList", this.getUserList());
        model.addAttribute("title", "刪除用戶");
        return new ModelAndView("users/list", "userModel", model);
    }

    /** * 修改用戶 * * @param id * @param model * @return */
    @GetMapping(value = "modify/{id}")
    public ModelAndView modifyForm(@PathVariable("id") Long id, Model model) {
        User user = userRepository.getUserById(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "修改用戶");
        return new ModelAndView("users/form", "userModel", model);
    }

}
複製代碼

5.3.三、前端編碼

  • list.html 展現用戶列表

  • form.html 新增或修改用戶資料

  • view.html 查看用戶資料

  • header.html 公用的頭部頁面

  • footer.html 公用的底部頁面

header 和 footer 咱們使用 thymeleaf 中的 th:fragment 特性來完成

一、在 src\main\resources\templates 目錄下中新建 fragments 目錄,用來存在模板文件。

footer.html

footer.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf in action</title>
</head>
<body>
<div data-th-fragment="footer">
    <a href="https://blog.csdn.net/runewbie">Welcome to blog.csdn.net</a>
</div>
</body>
</html>
複製代碼

header.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf in action</title>
</head>
<body>
<div data-th-fragment="header">
    <h1>Thymeleaf in action</h1>
    <a href="/users">首頁</a>
</div>
</body>
</html>
複製代碼

二、在 src\main\resources\templates 目錄下中新建users 目錄,存放業務相關的頁面文件。

list.html:展現用戶列表

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <title th:text="${userModel.title}">welcome</title>
</head>
<body>
<div th:replace="~{fragments/header :: header}">...</div>
<h3 th:text="${userModel.title}">Welcome to blog.csdn.net</h3>
<div>
    <a href="/users/form.html">建立用戶</a>
</div>
<table border="1">
    <thead>
    <tr>
        <td>ID</td>
        <td>Age</td>
        <td>Name</td>
    </tr>
    </thead>
    <tbody>
    <tr th:if="${userModel.userList.size()} eq 0">
        <td colspan="3">沒有用戶信息!!</td>
    </tr>
    <tr th:each="user : ${userModel.userList}">
        <td th:text="${user.id}">1</td>
        <td th:text="${user.age}">11</td>
        <td><a href="view.html" th:href="@{'/users/' + ${user.id}}" th:text="${user.name}">waylau</a></td>
    </tr>
    </tbody>
</table>
<div th:replace="~{fragments/footer :: footer}">...</div>
</body>
</html>
複製代碼

form.html:提交用戶表單

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <title th:text="${userModel.title}">users : View</title>
</head>
<body>
<div th:replace="~{fragments/header :: header}">...</div>
<h3 th:text="${userModel.title}">Welcome to blog.csdn.net</h3>
<div>
    <a href="/users">返回主頁</a>
</div>
<form action="/users" method="POST" th:object="${userModel.user}">
    <input type="hidden" name="id" th:value="*{id}">
    名稱:<br>
    <input type="text" name="name" th:value="*{name}">
    <br>
    年齡:<br>
    <input type="text" name="age" th:value="*{age}">
    <input type="submit" value="提交">
</form>
<div th:replace="~{fragments/footer :: footer}">...</div>
</body>
</html>
複製代碼

view.html:查看用戶信息

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <title th:text="${userModel.title}">users : View</title>
</head>
<body>
<div th:replace="~{fragments/header :: header}">...</div>
<h3 th:text="${userModel.title}">Welcome to blog.csdn.net</h3>
<div>
    <a href="/users">返回主頁</a>
</div>
<div>
    <p><strong>ID:</strong><span id="id" th:text="${userModel.user.id}">123</span></p>
    <p><strong>Name:</strong><span id="name" th:text="${userModel.user.name}">waylau</span></p>
    <p><strong>Age:</strong><span id="age" th:text="${userModel.user.age}">30</span></p>
</div>

<div>
    <a th:href="@{'/users/delete/' + ${userModel.user.id}}">刪除 </a>
    | <a th:href="@{'/users/modify/' + ${userModel.user.id}}">修改</a>
</div>
<div th:replace="~{fragments/footer :: footer}">...</div>
</body>
</html>
複製代碼

三、啓動測試項目

在完成以前12的操做以後,咱們能夠啓動項目來測試一下,啓動後若是系統正常,能夠看到下面的界面:

image-20191124220740131

咱們點擊建立用戶新建幾條數據:

image-20191124221202984

插入數據以後的效果,能夠看到 ID 是自增的:

image-20191124221241097

點擊某一條 name 能夠數據的修改和刪除操做:

image-20191124221442274

至此,咱們關於 Thymeleaf 的入門實戰就介紹完畢了。可能有人注意到了,個人頁籤帶有一個自定義的圖標:

image-20191124221639460

這個圖標是怎麼添加的呢?其實很簡單,就是在 src\main\resources\static 目錄下添加一個本身喜歡的 favicon.ico 文件便可。ico 格式文件能夠照一張圖片修更名稱便可獲取。而後從新啓動服務就能夠看到圖標,若是看不到,是由於瀏覽器存在緩存,能夠關閉瀏覽器從新打開對應頁面便可。

以上就是咱們的 Thymeleaf 學習,更對內容能夠查看源代碼獲取。

源代碼

thymeleaf-in-action

相關文章
相關標籤/搜索