【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

相关文章
相关标签/搜索