65.9K
CodeProject 正在变化。 阅读更多。
Home

ThymeLeaf 入门 - 与 Spring Boot 集成

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2020年11月5日

MIT

10分钟阅读

viewsIcon

8215

downloadIcon

106

Thymeleaf 与 Spring Boot Web 应用程序集成的基本用法,以及 Thymeleaf 标记的四种基本用法。

引言

在我之前为自己完成的一个大项目中,我使用了 Spring Boot,并将应用程序打包成 war 文件,以便它可以使用 TagLib。该项目取得了成功。但有一个未解决的问题。当在 Java 9 或更高版本下运行时,TagLib jar 文件会抛出异常。没有替代的 jar 文件可以解决这个问题。我喜欢 TagLib 和它的模板,以及 HTML 片段的模块化。如果这个 jar 文件将来会给我带来麻烦,我需要一个解决方案。

解决方案是 ThymeLeaf。它是在 201x 年的某个时候出现的。我不记得具体时间了。我当时没有去学习,因为我可以用 TagLib 完成我需要的所有设计。对于任何框架,我都可以利用其 10% 的能力来解决 95% 的现有问题。这是一项很实用的技能。现在 TagLib 将成为一个问题,是时候转向 ThymeLeaf 了。这是进化的一个部分。

在本教程中,我想讨论如何设置一个包含 ThymeLeaf 库的 Spring Boot 应用程序,如何创建可重用片段并在实际页面中使用它们。本教程还将讨论可以解决最常见设计问题的三个基本属性。

以下是页面正确渲染后的截图

Click to enlarge image

您看到的所有内容,标题栏、四个段落、无序列表、名为“显示此内容”的部分以及页脚(以及页面末尾的 JavaScript 文件)都是来自另一个文件的组件。如何重用组件是本教程的重点。在我们进一步探讨之前,让我们先看看 Maven POM 文件。

Maven POM 文件

示例项目的 Maven POM 文件是标准的 Spring Boot 项目,使用 Spring Boot starter parent 作为其父级。

为了编译和打包应用程序,Maven POM 文件应具有以下两个依赖项

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

第一个依赖项是 Spring Boot Web 应用程序的启动器。它负责构建一个简单 Web 应用程序的大部分引导工作。

第二个依赖项是 ThymeLeaf 与 Spring Boot 的集成。它负责应用程序中 ThymeLeaf 页面渲染的配置。

有了这两个 jar,设置 Spring 配置以使应用程序运行的困难部分都得到了解决。这为程序员节省了大量时间。

项目文件夹结构

对于这个项目,我使用了与以前相同的项目文件结构。Java 文件位于 *src/main/java/* 文件夹下的子文件夹中。HTML 页面模板文件位于 *src/main/resources/templates* 中。ThymeLeaf 模板使用的片段位于 *src/main/resources/templates/parts* 中。CSS 和 JavaScript 文件等静态文件位于 *src/main/resources/static/* 的子文件夹中。

这些文件夹位置是基于默认配置的。我确信可以在 *application.properties* 文件中更改这些位置。这些可以通过在线搜索找到。

Java 代码

为了运行这个示例应用程序,只需要两个 Java 文件。一个是主入口文件 *App.java*。另一个文件是控制器类文件 *IndexController.java*。

主入口文件看起来像这样

package org.hanbo.spring.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App
{
   public static void main(String[] args)
   {
      SpringApplication.run(App.class, args);
   }
}

这是使用 Spring Boot 启动 Spring Web 应用程序的典型方式。为了使应用程序功能更丰富,我可以添加更多的注解,但对于一个简单的示例应用程序来说,这足以让应用程序运行起来。

另一个文件是控制器类。在其中,有两个方法,每个方法都用于处理 HTTP 请求

package org.hanbo.spring.sample.controllers;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class IndexController
{
   @RequestMapping(value="/", method=RequestMethod.GET)
   public String home()
   {
      final String retVal = "redirect:/mixedup";
      
      return retVal;
   }

   @RequestMapping(value="/mixedup", method=RequestMethod.GET)
   public ModelAndView mixedup()
   {
      List<String> items = new ArrayList<String>();
      items.add("Simple Item 1");
      items.add("Simple Item 2");
      items.add("Simple Item 3");
      items.add("Simple Item 4");
      items.add("Simple Item 5");
      
      ModelAndView retVal = new ModelAndView();
      retVal.setViewName("mixedup");
      retVal.addObject("siteName", "Thymeleaf Sample");
      retVal.addObject("listTitle",
         "<span style=\"color: green;\">A Test Unordered List</span>");
      retVal.addObject("items", items);
      retVal.addObject("conditionVal", 5);
      
      return retVal;
   }
}

第二个请求处理对 URL:https://:8080/mixedup 的 HTTP GET 请求。

第二个方法返回一个 ModelAndView 对象。该对象包含视图的名称,即模板页面文件(包含所有占位符)。该对象还包含数据模型,一个对象的哈希映射,可以将其设置为页面模板上占位符的值。

该请求将触发使用 ThymeLeaf 标记创建的页面模板。如简介中所述,我创建了这个示例应用程序来实践一些简单的方面,这些方面应该可以解决我将面临的 95% 的设计问题。这些简单的方面包括

  • 如何使用 Java 方法返回的模型数据设置页面上元素的 HTML 文本
  • 如何遍历项目集合并在页面上显示它们
  • 如何有条件地显示元素
  • 如何将另一个文件中的 HTML 组件添加到页面中,并使用 Java 代码中的数据模型来设置元素文本。最后一部分是将页面的部分进行剪切和切片,将它们定义为组件,以便它们可以在不同的页面中使用。

接下来将讨论模板页面如何与数据模型中的值协同工作。

ThymeLeaf 页面模板

对于这个示例应用程序,我只有一个页面。它名为 *mixedup.html*,位于 *src/main/resouces/templates.html* 中。此页面由几个不同的部分构成。这些部分或组件具有由控制器类中的方法渲染的占位符。

页面的完整源代码如下所示

<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>HanBo-ORG Mixedup Page</title>
    <link rel="stylesheet" th:href="@{/bootstrap/css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{/bootstrap/css/bootstrap-theme.min.css}"/>
    <link rel="stylesheet" th:href="@{/css/index.css}"/>
</head>
<body>
   <div th:replace="components/parts::header">
   </div>
   
   <div class="container">
      <div th:insert="components/parts::info">
      </div>
      <div th:replace="components/parts::looptest">
      </div>
      <div th:if="${conditionVal == 5}">
         <div th:replace="components/parts::showThis"></div>
      </div>
      <div th:unless="${conditionVal == 5}">
         <div th:replace="components/parts::showThat"></div>
      </div>
   </div>
    
   <div th:replace="components/parts::footer"></div>
   
   <div th:replace="components/parts::stdjs"></div>
 </body>
</html>

让我们从头开始。对于任何渲染引擎,都需要导入渲染引擎的命名空间,以便它在渲染处理过程中找到特殊的标签。对于 ThymeLeaf,这是它的完成方式

<html lang="en" xmlns:th="http://www.thymeleaf.org">

然后,在 head 部分,这是第一次使用 ThymeLeaf 标记的地方

    <link rel="stylesheet" th:href="@{/bootstrap/css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{/bootstrap/css/bootstrap-theme.min.css}"/>
    <link rel="stylesheet" th:href="@{/css/index.css}"/>

在这种情况下,我使用了属性 th:href。并将 string 常量作为 URL 传递给这三行。

接下来,我使用在另一个文件中定义的部分,将其插入或放置到此页面文件中定义的位置,如下所示

   <div th:replace="components/parts::header">
   </div>

还有一些类似的用法

      <div th:replace="components/parts::looptest">
      </div>
      <div th:if="${conditionVal == 5}">
         <div th:replace="components/parts::showThis"></div>
      </div>
      <div th:unless="${conditionVal == 5}">
         <div th:replace="components/parts::showThat"></div>
      </div>
...
   <div th:replace="components/parts::footer"></div>
   
   <div th:replace="components/parts::stdjs"></div>

从这些行中取一行,属性名为 th:replace。它也可以是 th:insert。还有 th:include,它已弃用,很快将不再可用。

这些属性的值指定了部件文件在哪里以及该文件中的哪个代码片段将放置在此处。例如,值“components/parts::looptest”表示包含可重用部件的文件位于模板文件夹中的子文件夹“components”中,文件名为“parts.html”。:: 分隔符指定了在 parts 文件中可以找到片段的位置。在此示例中,部件文件中的片段名为“looptest”。

此文件的最后一个重要部分是值的条件放置,类似于 if ... else ... 块。这是使用 ThymeLeaf 的实现方式

      <div th:if="${conditionVal == 5}">
         ...
      </div>
      <div th:unless="${conditionVal == 5}">
         ...
      </div>

请注意,else 块(属性 th:unless)必须与 if 的条件检查具有相同的条件检查。如果您使用不同的条件检查,显示将非常奇怪。

是时候看看片段是什么样子了。这些片段定义在一个名为 *parts.html* 的文件中。我们接下来将讨论这个。

ThymeLeaf 片段

为了构建一个带有可重用部件的页面,这些部件必须放置在某个位置,以便在页面渲染时,这些部件可以从这个公共位置提取并添加到页面中。ThymeLeaf 的做法不同。可重用部件可以打包在一个文件中。每个部件都有一个 ID。然后,在构建的页面中,这些片段可以通过目标文件夹、部件文件和片段 ID 进行引用。

回想一下上一节中,部件被替换、插入或包含(以传统方式)。它可能看起来像这样

   <div th:replace="components/parts::header">
   </div>

属性 th:replace 的值为“components/parts::header”。值“components/parts”是定义片段的文件。如果您认为该文件名为“components/parts.html”,那么您是对的。查找文件的基本路径是“resources/templates”。因此相对完整路径是“resources/templates/components/parts.html”。该文件看起来像这样

<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8"/>
      <title>Spring Boot Thymeleaf Application - Fragments</title>
   </head>
   <body>
      <div th:fragment="header">
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" 
          data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" th:href="@{/}" th:text="${siteName}"></a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
        </div>
      </div>
    </nav>
      </div>

      <div class="row page-start" th:fragment="info">
         <div class="col-xs-12">
            <p>
            Lorem ipsum dolor sit amet, per eu natum probatus, no habeo posse invidunt eos. 
            Qui ad audire vivendum detraxit, quod dico vocibus pri in, et purto feugait vim. 
            Ius causae ceteros dolores in, at noster delenit nam. 
            Aliquip integre offendit sit ut.
            </p>
            <p>
            Graecis definitiones et pri. 
            Postea detraxit nec ei, audiam diceret maluisset eam cu. 
            Ut his etiam minim semper, duis postea epicuri nec id, 
            an maiestatis vituperata his. 
            Ei sea verear dissentias, qui simul senserit efficiantur te. 
            Te dicam soluta nam, 
            ea eum persius iudicabit. Eu omnes offendit splendide pro, 
            discere definitionem vel id, 
            veritus habemus quaestio ad quo.
            </p>
            <p>
            Nam an melius consequat, id nam inermis accusata reprehendunt, 
            qui eu quem unum omnium. 
            Per enim nostrud et, quodsi omnesque referrentur at usu. 
            Ut qui gubergren reprehendunt, 
            ne alia veritus vis. Ut has cibo mediocrem consequuntur. Mundi facilisi eam an.
            </p>
            <p>
            Ex eos movet persequeris referrentur. Essent mediocritatem eu eos. 
            Sea at elit vulputate, alia ludus choro vim id, 
            mel at munere moderatius definitiones. Mei eu debet partem ubique, 
            cu verear noluisse mei. Eum ullum dictas consulatu an, 
            dicunt delicatissimi ius te, ne feugait tincidunt has.
            </p>
            <p>
            Ex posse perfecto sit, soluta ocurreret scribentur ut sea. 
            Admodum intellegam at nec, eam ex dictas accusam dolores. 
            Ut nec dicta veritus, in meis verear fuisset vix, 
            sea in solum tantas virtute. No mea agam graecis, an adhuc everti senserit eam, 
            ne qui dolore legere fastidii. Usu ornatus dissentiunt ex. In errem dicunt pri.
            </p>
         </div>
      </div>

      <div class="row" th:fragment="looptest">
         <div class="col-xs-12">
            <h3 th:utext="${listTitle}"></h3>
            <ul th:each="item: ${items}">
               <li th:text="${item}"></li>
            </ul>
         </div>
      </div>
      
      <div class="row" th:fragment="showThis">
         <div class="col-xs-12">
            <h3>Show This</h3>
            <p>This is one paragraph.</p>
         </div>
      </div>
      
      <div class="row" th:fragment="showThat">
         <div class="col-xs-12">
            <h3>Show This</h3>
            <p>This is another paragraph.</p>
         </div>
      </div>

      <div class="container-fluid" th:fragment="footer">
         <div class="row footer">
            <div class="col-xs-12">
               <hr/>
               &copy 2020, hanbo.org.
            </div>
         </div>
      </div>
      
      <div th:fragment="stdjs">
   <script type="text/javascript" th:src="@{/jquery/js/jquery.min.sj}"></script>
   <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.sj}"></script>
      </div>
   </body>
</html>

在这个文件中,片段定义如下

<div class="row" th:fragment="showThat">
...
</div>

属性 th:fragment 设置片段的 ID。此 ID 是在作用域分隔符“::”之后引用的 ID。当此片段渲染到实际页面时,整个 div 元素及其所有子元素都将渲染到页面。然后,整个页面的占位符将使用控制器方法返回的 ModelAndView 对象中的实际值进行设置。

下一个问题是,我们如何为占位符设置值?在这个示例中,有四种不同的方式

  • 为 HTML 元素放置文本
  • 为 HTML 元素放置未转义的文本
  • 遍历元素集合并将其渲染为文本
  • 以及我们在上一节中看到的 if-else 条件块

另一个值得学习的概念是 switch-case 块,它非常简单,所以我这里就不介绍了。

首先,如何将 ModelAndView 中的基于文本的值显示到渲染页面。文本 string 定义如下

...

ModelAndView retVal = new ModelAndView();
...
retVal.addObject("siteName", "Thymeleaf Sample");

将此文本渲染到 HTML 元素的方法如下

...
<a class="navbar-brand" ... th:text="${siteName}"></a>
...

除了显示纯文本,显示 HTML 文本并使其渲染也很有用。这很容易实现,如下所示。假设 HTML string 的 Java 代码定义如下

...

ModelAndView retVal = new ModelAndView();
...
retVal.addObject("listTitle",
   "<span style=\"color: green;\">A Test Unordered List</span>");
...

要渲染 HTML string,方法如下

...
<h3 th:utext="${listTitle}"></h3>
...

在这两种情况下,我都使用“${variableName}”来引用存储在 ModelAndView 对象中的对象/变量。这些是 ModelAndView 对象中“hashmap”内部真实值的键。

要渲染列表或其它可迭代集合中的 string 值列表。以下是实现方法。假设定义列表对象的 Java 代码如下

List<String> items = new ArrayList<String>();
items.add("Simple Item 1");
items.add("Simple Item 2");
items.add("Simple Item 3");
items.add("Simple Item 4");
items.add("Simple Item 5");

ModelAndView retVal = new ModelAndView();
...
retVal.addObject("items", items);
...

然后列表中的这些项的渲染方式如下

...
<div class="col-xs-12">
   ...
   <ul th:each="item: ${items}">
      <li th:text="${item}"></li>
   </ul>
</div>
...

这与单个值文本的显示略有不同。首先,您必须获取集合的迭代器。这是通过属性 th:each 完成的,然后像在 for 循环中一样使用迭代器变量,通过“${item}”获取真实值。

我们已经看到了 if-else 语句的使用。让我们再回顾一下。假设变量和值的 Java 代码设置如下

ModelAndView retVal = new ModelAndView();
...
retVal.addObject("conditionVal", 5);

if-else 的工作方式是这样的

   <div th:if="${conditionVal == 5}">
      ...
   </div>
   <div th:unless="${conditionVal == 5}">
      ...
   </div>

第一部分很容易理解。如果变量“conditionVal”等于 5。第二部分有点难以理解。它使用 th:unless。它基本上表示除非变量“conditionVal”等于 5,否则它将显示;如果它等于 5,那么它将不显示。正如我之前所说,在两种条件下,要检查的值必须相同。否则渲染将显示非常奇怪的内容。

如何运行此示例应用程序

要构建此示例应用程序,请使用命令提示符并转到 *POM.xml* 文件所在的文件夹。然后运行以下命令

mvn clean install

构建成功后(因为这是一个简单的应用程序,所以会成功),运行以下命令。它将启动 Web 应用程序

java -jar target\hanbo-thymeleaf-sample-1.0.1.jar

Web 应用程序将成功启动。之后,使用 Web 浏览器访问以下 URL

https://:8080/

然后您将看到本教程开头的截图。本教程到此结束。

摘要

在本教程中,我讨论了如何将 ThymeLeaf 页面渲染引擎与由 Spring Boot 支持的 Web 应用程序集成。我的主要目标是找到我正在使用的现有渲染引擎的替代方案。这也是一个学习新事物的机会。我对这个新渲染引擎的印象是:

  • 将 ThymeLeaf 与基于 Spring Boot 的 Web 应用程序集成非常容易。
  • 很容易自定义配置,这样您就不必依赖默认配置。
  • 渲染规则非常容易掌握。
  • 由于其用户友好性,我使用 ThymeLeaf 完成了我以前所做的任何事情都没有遇到任何麻烦。

我不确定的是与 Spring Security 的集成。Spring Security 有自己的标签命名空间,而 ThymeLeaf 也有自己的。两者导入命名空间的方式不同。我有点不确定这种差异以及它是否会产生任何问题。我确信应该不会有任何问题,因为两者都被许多项目很好地结合使用。我稍后会为此创建另一个教程。

我匆忙地创建了这个教程。这不是我最好的作品。它已经完成了。希望您喜欢。

历史

  • 2020/10/11 - 初稿
© . All rights reserved.