考虑到后面SpringMVC和SpringBoot中需要使用Thymeleaf技术,初步学习,做点笔记。
Thymeleaf官网帮助文档

创建maven工程,pom文件中导入Thymeleaf依赖


    <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf</artifactId>
      <version>3.0.12.RELEASE</version>
    </dependency>
public class HelloThymeleaf {
    public static void main(String[] args) {
        //创建模板引擎
        TemplateEngine engine  = new TemplateEngine();

        //准备模板
        //模拟网页表单对象
        String input = "<input type='text' th:value='hellothymeleaf'/>";

        //准备数据,使用context
        Context context = new Context();
        //调用引擎,处理模板和数据
        /*
        engine.process();
        参数一:模板
        参数二:Context对象
        */
        String out = engine.process(input, context);
        System.out.println("结果数据" + out);
    }
}

1.png

可以渲染数据,写成动态的

public class MyTest {
    @Test
    public void test01() {
        //创建模板引擎
        TemplateEngine engine = new TemplateEngine();

        //准备模板,name此时相当于一个占位符,提供数据,模板引擎会提供真实的数据
        String inStr = "<input type='text' th:value='${name}'/>";

        //准备数据
        Context context = new Context();
        //给name标识来指定数据
        context.setVariable("name", "李四");

        //处理模板和数据
        String html = engine.process(inStr, context);
        System.out.println("html = " + html);

    }
}

2.png


使用模板文件

    @Test
    public void test02(){
        TemplateEngine engine = new TemplateEngine();
        //读取磁盘中的模板文件
        ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();

        //设置引擎使用resolver
        engine.setTemplateResolver(resolver);

        //指定数据
        Context context = new Context();
        context.setVariable("name","张三");

        //处理模板
        String html = engine.process("main.html", context);
        System.out.println("html = " + html);
    }

简单写一个main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  模板:<input type="text" th:value="${name}" />
</body>
</html>

读取测试。
3.png


这样需要指定文件名+后缀的形式,而且位置固定
String html = engine.process("main.html", context);
如果使用的文件不是在resource类路径文件下,那么还需要指定文件的路径,简化模板文件所在的目录位置和扩展名的处理,使用解析器ClassLoaderTemplateResolver。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <input type="text" th:value="${name}">
</body>
</html>
    @Test
    public void test03() {
        TemplateEngine engine = new TemplateEngine();
        //创建模板引擎的解析器
        ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
        //指定前缀,相对于resources,目录自定义
        resolver.setPrefix("templates/");

        //指定后缀,文件扩展名
        resolver.setSuffix(".html");

        //设置引擎要用到的解析器
        engine.setTemplateResolver(resolver);
        Context context = new Context();
        context.setVariable("name","张三三");

        //路径制定好了,直接写名称就可以了
        String html = engine.process("index", context);
        System.out.println("html = " + html);

    }

4.png


SpringBoot整合

之前的内容基于MVC使用Thymeleaf,接下来在SpringBoot中整合Thymeleaf并使用
在没有职业前端,不使用前后端分离开发,使用服务端渲染的情况下,需要使用模板引擎。
前后端分离开发,分工明确,协同开发,项目上线速度快。
服务端渲染,少了一次前端交互服务端,项目速度快。
依然是导入pom文件

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • a. 所有的模板页面在classpath:/templates/ 下面找
  • b. 找后缀名为.html的页面

初体验

使用thymeleaf,通过get方式传参,在网页中显示对应的文字。

WelcomeController

/**
 * @author: TangZhiKai
 * @create: 2023-10-08 17:43
 * @description: 测试模板引擎Thymeleaf
 **/
@Controller //适配服务端渲染技术,也就是前后端不分离模式,不要使用RestController
public class WelcomeController {

    /**
     * 利用模板引擎跳转到指定页面
     * @param name
     * @param model 同以前MVC,使用Model,把所有要给页面共享的数据放到里面
     * @return
     */
    @GetMapping("/well")
    public String hello(@RequestParam("name") String name, Model model){

        //想要来到welcome.html页面,因为整合的Thymeleaf
        //默认是在类路径resource下的templates 后缀是.html。我们只需要补充上逻辑视图名就可以了
        //模板的逻辑视图地址,最终生成物理视图。物理视图 = 前缀 + 逻辑视图名 + 后缀
        //真实地址= classpath:/templates/welcome.html

        //把需要给页面共享的数据放入model中
        model.addAttribute("msg",name);
        return "welcome";
    }
}

效果
5.png


基本用法

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h1>你好:<span th:text="${msg}"></span></h1>
<hr/>
<!-- th: text 替换标签体的内容;会转义 -->
<!-- th: utext 替换标签体的内容;不会转义html标签,真正显示成html该有的样式 -->
<h1 th:text="${msg}">哈哈</h1>
<h1 th:utext="${msg}">呵呵</h1>

<hr/>
<!--th:任意html属性:动态替换为任意属性的值-->
<!--改为动态传递图片-->
<img th:src="${imgUrl}" src="1.png" style="width:300px;"/>
<br/>
<!--th:attr:任意属性指定,所有属性都可以在attr中任意取值.我们可以将想要的东西放到域共享数据中,通过指定格式取值-->
<img th:src="${imgUrl}" src="2.png" style="width:300px;" th:attr="src=${imgUrl},style=${style}"/>
<br/>
<!--th:其他指令 th:if 要不要显示 取出表达式,如果为true就显示,如果为false就不会显示-->
<img th:src="${imgUrl}" th:style="${style}" th:if="${show}">

</body>
</html>

HelloController

package com.atguigu.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author: TangZhiKai
 * @create: 2023-10-08 17:43
 * @description: 测试模板引擎Thymeleaf
 **/
@Controller //适配服务端渲染技术,也就是前后端不分离模式,不要使用RestController
public class WelcomeController {


    /**
     * 利用模板引擎跳转到指定页面
     * @param name
     * @param model 同以前MVC,使用Model,把所有要给页面共享的数据放到里面
     * @return
     */
    @GetMapping("/well")
    public String hello(@RequestParam("name") String name, Model model){

        //想要来到welcome.html页面,因为整合的Thymeleaf
        //默认是在类路径resource下的templates 后缀是.html。我们只需要补充上逻辑视图名就可以了
        //模板的逻辑视图地址,最终生成物理视图。物理视图 = 前缀 + 逻辑视图名 + 后缀
        //真实地址= classpath:/templates/welcome.html

        //把需要给页面共享的数据放入model中
        String text = "<span style='color:red'>"+name+"</span>";
        model.addAttribute("msg",text);

        //路径是动态的
        model.addAttribute("imgUrl","/1.png");
        //数据库查出的样式
        model.addAttribute("style","width:400px");

        model.addAttribute("show",false);
        return "welcome";
    }
}

表达式取值

  • ${}:变量取值;使用model共享给页面的值都直接用${}
  • @{}:url路径
<!--以后@{}专门用来取各种路径
@{${imgUrl}} 解释:先去${}model里面取出imgUrl,然后被@{}封装,自动加/不加 项目的路径这样会非常安全-->
<img src="/static/girl.png" style="width:300px;" th:src="@{/static/girl.png}">
  • #{}:国际化消息
  • ~{}:片段引用
  • *{}:变量选择:需要配合th:object绑定对象

遍历

语法: th:each="元素名,迭代状态 : ${集合}"

WelcomeController

    /**
     * 这里我创建一个集合通过thymeleaf语法遍历到html页面
     *
     * @param model 我需要把数据共享到页面,需要使用共享域
     * @return 跳转到list.html页面
     */
    @GetMapping("/list")
    public String list(Model model){
        List<Person> list = Arrays.asList(
                new Person(1L,"张三1","zs1@qq.com",15,"pm"),
                new Person(3L,"张三2","zs2@qq.com",15,"pm"),
                new Person(4L,"张三3","zs3@qq.com",15,"pm"),
                new Person(7L,"张三4","zs4@qq.com",15,"admin"),
                new Person(8L,"张三5","zs5@qq.com",15,"hr")
        );

        model.addAttribute("persons",list);
        return "list";
    }

list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>列表页</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>

<body>
<!--需求:将welcome中的list遍历转到当前列表页-->
<!--为了表格好看,我可以引入bootstrap样式-->
<table class="table">
    <thead>
    <tr>
        <th scope="col">#</th>
        <th scope="col">名字</th>
        <th scope="col">邮箱</th>
        <th scope="col">年龄</th>
        <th scope="col">角色</th>
        <th scope="col">状态信息</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="person,stats : ${persons}">
        <th scope="row" th:text="${person.id}">1</th>
        <td th:text="${person.userName}">Mark</td>
        <!--这里我们可以使用一下thymeleaf的行内写法-->
        <td> [[${person.email}]] </td>
        <td> [[${person.age}]] </td>
        <td th:text="${person.role}"></td>
        <td>
            index: [[${stats.index}]] <br/>
            count: [[${stats.count}]] <br/>
            size(总数量): [[${stats.size}]] <br/>
            current: [[${stats.current}]] <br/>
            even(true)/odd(false): [[${stats.even}]] <br/>
            first: [[${stats.first}]] <br/>
            last: [[${stats.last}]] <br/>
        </td>
    </tr>

    </tbody>
</table>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
        crossorigin="anonymous"></script>

</body>
</html>

效果图
6.png
在遍历中,还可以加入stats,查看当前遍历的行信息。(代码中使用了thymeleaf中的行内引用)

  • index:当前遍历元素的索引,从0开始
  • count:当前遍历元素的索引,从1开始
  • size:需要遍历元素的总数量
  • current:当前正在遍历的元素对象
  • even/odd:是否偶数/奇数行
  • first:是否第一个元素
  • last:是否最后一个元素

if,switch

list.html

    <tr th:each="person,stats : ${persons}">
        <th scope="row" th:text="${person.id}">1</th>
        <td th:text="${person.userName}">Mark</td>
        <!--这里我们可以使用一下thymeleaf的行内写法-->
        <td th:if="${#strings.isEmpty(person.email)}" th:text="'联系不上'">  </td>
        <td th:if="${not #strings.isEmpty(person.email)}" th:text="${person.email}"></td>
        <td th:text="|${person.age} / ${person.age >= 18 ? '成年':'未成年' }|"> </td>
        <td th:switch="${person.role}">
            <button th:case="'admin'" type="button" class="btn btn-danger">管理员</button>
            <button th:case="'pm'" type="button" class="btn btn-primary">项目经理</button>
            <button th:case="'hr'" type="button" class="btn btn-default">人事</button>
        </td>
    </tr>

属性选择

我们可以将域对象共享的数据,或者是session作用域中的数据,直接放到标签体,用th:object标签来添加,这样添加的好处就是,在标签体中,当我们需要对象的数据的时候,我们可以直接使用*{成员属性}来取代 ${对象.成员属性}的形式。

<tr th:each="person,stats : ${persons}" th:if="${person.age > 10}" th:object="${person}">
        <th scope="row" th:text="${person.id}">1</th>
        <!--<td th:text="${person.userName}">Mark</td>-->
        <td th:text="*{userName}">Mark</td>
    </tr>

最后当前list.html的全部代码我放在此处:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>列表页</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>

<body>
<!--导航区-->
<div th:replace="~{common :: myheader}"></div>
<!--<header class="p-3 text-bg-dark">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                <svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap">
                    <use xlink:href="#bootstrap"></use>
                </svg>
            </a>

            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a href="#" class="nav-link px-2 text-secondary">Home</a></li>
                <li><a href="#" class="nav-link px-2 text-white">Features</a></li>
                <li><a href="#" class="nav-link px-2 text-white">Pricing</a></li>
                <li><a href="#" class="nav-link px-2 text-white">FAQs</a></li>
                <li><a href="#" class="nav-link px-2 text-white">About</a></li>
            </ul>

            <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
                <input type="search" class="form-control form-control-dark text-bg-dark" placeholder="Search..."
                       aria-label="Search">
            </form>

            <div class="text-end">
                <button type="button" class="btn btn-outline-light me-2">Login</button>
                <button type="button" class="btn btn-warning">Sign-up</button>
            </div>
        </div>
    </div>
</header>-->
<div class="container">
    <table class="table">
        <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">名字</th>
            <th scope="col">邮箱</th>
            <th scope="col">年龄</th>
            <th scope="col">角色</th>
            <th scope="col">状态信息</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="person,stats : ${persons}" th:if="${person.age > 10}" th:object="${person}">
            <th scope="row" th:text="${person.id}">1</th>
            <!--<td th:text="${person.userName}">Mark</td>-->
            <td th:text="*{userName}">Mark</td>
            <!--这里我们可以使用一下thymeleaf的行内写法-->
            <td th:if="${#strings.isEmpty(person.email)}" th:text="'联系不上'">  </td>
            <td th:if="${not #strings.isEmpty(person.email)}" th:text="${person.email}"></td>
            <td th:text="|${person.age} / ${person.age >= 18 ? '成年':'未成年' }|"> </td>
            <td th:switch="${person.role}">
                <button th:case="'admin'" type="button" class="btn btn-danger">管理员</button>
                <button th:case="'pm'" type="button" class="btn btn-primary">项目经理</button>
                <button th:case="'hr'" type="button" class="btn btn-default">人事</button>
            </td>

            <td>
                index: [[${stats.index}]] <br/>
                count: [[${stats.count}]] <br/>
                size(总数量): [[${stats.size}]] <br/>
                current: [[${stats.current}]] <br/>
                even(true)/odd(false): [[${stats.even}]] <br/>
                first: [[${stats.first}]] <br/>
                last: [[${stats.last}]] <br/>
            </td>
        </tr>

        </tbody>
    </table>
</div>

<!--需求:将welcome中的list遍历转到当前列表页-->
<!--为了表格好看,我可以引入bootstrap样式-->


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
        crossorigin="anonymous"></script>

</body>
</html>

片段引用

片段引用我录制了视频,用于解释。


devtools

如果在后台IDEA修改页面,那么当前页面存在缓存,刷新是没有效果的。我们可以在pom文件中导入SpringBoot给我们整合好的一个热启动工具devtools

<!--热启动功能-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
  • CTRL + F9 重新编译

以后不仅是页面,Java代码修改后,只要CTRL + F9 重新编译,就可以重新启动。
推荐修改页面后,使用devtools; 对于代码的修改,还是重新手动启动。java代码的修改,如果devtools热启动,可能会引起一些bug


我书写这篇文章的初衷就是总结学习的进度,遗忘之际可以拿出来翻看,如有不对的地方还望指正,多多海涵。