前言
本篇记录我在学习SpringBoot的过程中,对Thymeleaf的学习经验;Thymeleaf是一种
提示:以下是本篇文章正文内容,下面案例可供参考
一 Thymeleaf 介绍
1.1 概念
Thymeleaf 是一个跟 FreeMarker 类似的模板引擎,它可以完全替代 JSP 。;
1.2 特点
-
动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,无网络显示静态内容,有网络用后台得到数据替换静态内容;
-
与SpringBoot完美整合,springboot默认整合thymelea
二 Thymeleaf详解
1 表达式
Thymeleaf中的表达式分为三种
1.1 变量表达式
就是OGNL表达式或者Spring EL表达式(spring中在jsp页面中获取session或其他内置对象的数据)如:${session.user.name};
它们将以HTML标签的一个属性来表示:
<h5>表达式</h5>
<span>${text}</span>
<span th:text="${text}">你好 thymleaf</span>
1.2 选择表达式
也可以叫星号表达式,有点像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,如: *{customer.name};被指定的对象object由th:object属性定义,
代码如下:
<!-- user 循环对象 status 循环的序号 -->
<tr th:each="user, status : ${users}" th:object="${user}" th:bgcolor="${status.even}? 'gray'">
<td th:text="${status.even}">true</td>
<td th:text="${status.odd}">true</td>
<td th:text="${status.index}">0</td>
<td th:text="${status.count}">1</td>
<td th:text="${user.id}">1</td>
<td th:text="*{name}">zhangsan</td>
<td th:text="*{userName}">张三</td>
<td th:text="${user.age}">20</td>
<td th:text="${user.sex} == 1 ? '男' : '女'">男</td>
<td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd')}">1980-02-30</td>
<td th:text="${user.note}">1</td>
<td>
<!--<a href="">删除</a>-->
<a th:href="@{delete(id=${user.id}, userName= *{userName})}">删除</a>
<!-- 字符串替换 -->
<a th:href="|/update/${user.id}|">修改</a>
<!-- 字符串拼接 -->
<a th:href="'approve/' + ${user.id}">审核</a>
</td>
</tr>
1.3 URL表达式
- URL表达式指的是把一个有用的上下文或回话信息添加到URL,这个过程经常被叫做URL重写。
- URL还可以设置参数: @{/order/details(id=${orderId}, name=*{name})
1.3.1 url表达式
<a th:href="@{/delete(id=${user.id}, userName=*{userName})}">删除</a>
1.3.2 文本替换
<a th:href="|/update/${user.id}|">修改</a
1.3.2 字符串拼接
<a th:href="'/approve/' + ${user.id}">审核</a>
1.4 表达式常见用法
字面(Literals)
- 文本文字(Text literals): ‘one text’, ‘Another one!’,…
- 数字文本(Number literals): 0, 34, 3.0, 12.3,…
- 布尔文本(Boolean literals): true, false
- 空(Null literal): null文字标记(Literal tokens): one, sometext, main,…
- 文本操作(Text operations)
- 字符串连接(String concatenation): +
- 文本替换(Literal substitutions): |The name is ${name}|
- 算术运算(Arithmetic operations)
- 二元运算符(Binary operators): +, -, *, /, %
- 减号(单目运算符)Minus sign (unary operator): -
- 布尔操作(Boolean operations)
- 二元运算符(Binary operators): and, or
- 布尔否定(一元运算符)Boolean negation (unary operator): !, not
- 比较和等价(Comparisons and equality)
- 比较(Comparators): >, <, >=, <= (gt, lt, ge, le)
- 等值运算符(Equality operators): ==, != (eq, ne)
- 条件运算符(Conditional operators)
- If-then: (if) ? (then)
- If-then-else: (if) ? (then) : (else)
- Default: (value) ?: (defaultvalue)
1.5 常用th标签
关键字 | 功能介绍 | 案例 |
---|---|---|
th:text | 文本替换 | < p th:text="${collect.description}">description< /p > |
th:id | 替换id | < input th:id="‘xxx’ + ${collect.id}"/ > |
th:utext | 支持html的文本替换 | < p th:utext="${htmlcontent}">conten< /p > |
th:object | 替换对象 | < div th:object="${session.user}"> |
th:value | 属性赋值 | < input th:value="${user.name}" /> |
th:with | 变量赋值运算 | < div th:with=“isEven=${prodStat.count}%2==0”>< /div> |
th:style | 设置样式 | th:style="‘display:’ + @{(${sitrue} ? ‘none’ : ‘inline-block’)} + ‘’" |
th:onclick | 点击事件 | th:οnclick="‘getCollect()’" |
th:each | 属性赋值 | tr th:each=“user,userStat:${users}”> |
th:if | 判断条件 | < a th:if="${userId == collect.userId}" > |
th:unless | 和th:if判断相反 | < a th:unless="${userId == collect.userId}" > |
th:href | 链接地址 | <a th:href=" |
th:switch | 多路选择 配合th:case使用 | < div th:switch="${user.role}" |
th:case | th:switch的一个分支 | < p th:case="‘admin’">User is an administrator< /p> |
th:fragment | 布局标签,定义一个代码片段,方便其它地方引用 | < div th:fragment=“alert”> |
th:include | 布局标签,替换内容到引入的文件 | < head th:include=“layout :: htmlhead” th:with=“title=‘xx’”>< /head> |
th:replace | 布局标签,替换整个标签到引入的文件 | < div th:replace=“fragments/header :: title”>< /div> |
th:selected | selected选择框 选中 | th:selected="( {configObj.dd})" |
th:src | 图片类地址引入< img class=“img-responsive” alt=“App Logo” th:src="@{/img/logo.png}" /> | |
th:inline | 定义js脚本可以使用变量 | < script type=“text/javascript” th:inline=“javascript”> |
th:action | 表单提交的地址 | < form action=“subscribe.html” th:action="@{/subscribe}"> |
th:remove | 删除某个属性 | < tr th:remove=“all”> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。 |
th:attr | 设置标签属性,多个属性可以用逗号分隔 | th:attr=“src=@{/image/aa.jpg},title=#{logo}”,此标签不太优雅,一般用的比较少。 |
1.6 基本用法
1.6.1 赋值、字符串拼接
<a th:href="|/update/${user.id}|">修改</a>
<a th:href="'/approve/' + ${user.id}">审核</a>
1.6.2 条件判断if/unless
if是条件成立是生效,unless是条件不成立是生效,两者恰好相反
<h5>if指令</h5>
<a th:if="${users.size() > 0}">查询结果存在</a><br>
<a th:if="${users.size() <= 0}">查询结果不存在</a><br>
<a th:unless="${session.user != null}" href="#">登录</a><br>
1.6.3 for循环
<!-- user 循环对象 status 循环的序号 -->
<tr th:each="user, status : ${users}" th:object="${user}" th:bgcolor="${status.even}? 'gray'">
<td th:text="${status.even}">true</td>
<td th:text="${status.odd}">true</td>
<td th:text="${status.index}">0</td>
<td th:text="${status.count}">1</td>
<td th:text="${user.id}">1</td>
<td th:text="*{name}">zhangsan</td>
<td th:text="*{userName}">张三</td>
<td th:text="${user.age}">20</td>
<td th:text="${user.sex} == 1 ? '男' : '女'">男</td>
<td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd')}">1980-02-30</td>
<td th:text="${user.note}">1</td>
<td>
<!--<a href="">删除</a>-->
<a th:href="@{delete(id=${user.id}, userName= *{userName})}">删除</a>
<!-- 字符串替换 -->
<a th:href="|/update/${user.id}|">修改</a>
<!-- 字符串拼接 -->
<a th:href="'approve/' + ${user.id}">审核</a>
</td>
</tr>
1.6.4 status状态变量
- index:当前迭代对象的index(从0开始计算)
- count: 当前迭代对象的index(从1开始计算)
- size:被迭代对象的大小
- current:当前迭代变量
- even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
- first:布尔值,当前循环是否是第一个
- last:布尔值,当前循环是否是最后一个
1.6.5 内联文本
以**[[…]]**方式表示,使用时必须使用“th:inline=”text”激活,th:inline可以在
父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不利于原型显示。使用的内容必须是后端返回的model总有的内容;
- 在thymeleaf指令中显示
<h6 th:text="${text}">静态内容</h6>
- 使用内联文本显示model attribute
<h5>内联文本</h5>
<div>
<h6 th:inline="text">[[${text}]]</h6>
<h6 th:inline="none">[[${text}]]</h6>
<h6>[[${text}]]</h6>
</div>
**PS:**能用th指令就用替换指令
1.6.6 内联文本
与内联文本相似,以th:inline=”text/javascript/none”激活,内容是js脚本。拥有js脚本的能力;
<h5>内联js</h5>
<script th:inline="javascript">
var text = '[[${text}]]';
console.log(text);
</script>
1.6.7 内嵌对象
Thymeleaf提供了了一系列Utility对象(内置于Context中),可以通过**# + 对象名**的方式直接调用其中的方法。
对象 | 功能 |
---|---|
dates | java.util.Date**的功能方法类。 |
calendars | 类似#dates,面向java.util.Calendar |
numbsers | 格式化数字的功能方法类 |
strings | 字符串对象的功能类 |
objects | 对objects的功能类操作。 |
bools | 对布尔值求值的功能方法。 |
arrays | 对数组的功能类方法。 |
lists/sets/maps/ | 对list/set/map功能类方法 |
1.6.8 布局
通过footer标签定义页面;
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy(title)">
© 2020 开课吧版权所有<br>
<span th:text="${title}">title footer</span>
</footer>
</body>
</html>
然后,在页面任何地方引入:
<h5>thymeleaf布局</h5>
<div th:insert="footer :: copy('开课吧1')"></div>
<div th:replace="footer :: copy('开课吧2')"></div>
<div th:include="footer :: copy('开课吧3')"></div>
- th:insert :保留自己的主标签,保留th:fragment的主标签。
- th:replace :不要自己的主标签,保留th:fragment的主标签。
- th:include :保留自己的主标签,不要th:fragment的主标签。
三 Thymeleaf入门案例
1启动器
Thymelea启动器在依赖坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2 pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kaikeba</groupId>
<artifactId>sb-thymeleaf-01</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.11.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- tk-mybatis 非官方提供 手动控制版本 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
</dependencies>
</project>
3 配置文件
application.properties
server.port=8080
logging.level.com.kaikeba=debug
# 数据库信息
spring.datasource.url=jdbc:mysql://localhost:3306/springboot-jdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 关闭thymeleaf缓存
spring.thymeleaf.cache=false
4 相关的静态文件
由于springboot为thymeleaf自动添加的视图解析器会加上templates前缀,所以我们的所有静态文件都必须在**/resources/templates/** 下,
定义的布局footer.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy(title)">
©2020开课吧版权所有<br/>
<span th:text="${title}">title footer</span>
</footer>
</body>
</html>
测试的temp.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>Thymeleaf布局</h2>
<!-- 把footer标签添加到主(父)标签里面 -->
<div th:insert="footer :: copy('开课吧1')"></div>
<!-- 直接替换主(父)标签 -->
<div th:replace="footer :: copy('开课吧22')"></div>
<!-- 直接把内容覆盖到主(父)标签的内容中 -->
<div th:include="footer :: copy('开课吧333')"></div>
<h2>内置变量</h2>
<span th:text="${#dates.format(#dates.createNow(), 'yyyy-MM-dd HH:mm:ss')}">获取当前日期</span><br/>
<span th:text="${#strings.substring(text, 4, 6)}">截取字符串</span><br/>
<span th:text="${#strings.length(text)}"></span><br/>
<span th:text="${#strings.randomAlphanumeric(6)}">随机字符串</span><br/>
<span th:text="${#strings.equals(text, 'hello')}">equals等值判断</span><br/>
<h2>if指令</h2>
<a th:if="${users.size() > 0}"> 查询结果存在</a><br/>
<a th:if="${users.size() <= 0}">查询结果不存在</a><br/>
<a th:unless="${session.user != null}">登录</a><br/>
<h2>内联文本</h2>
<span th:text="${text}">静态内容</span>
<div>
<h3 th:inline="text">${text}</h3>
<h3>[[${text}]]</h3>
</div>
<h3>内联JS</h3>
<span>内容在console.log</span>
<script th:inline="javascript">
var text = '[[${text}]]';
console.log(text);
</script>
</body>
</html>
测试循环的user.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf 入门案例</title>
<meta charset="UTF-8">
<style type="text/css">
table {
border-collapse: collapse;
font-size: 15px;
width: 80%;
margin: auto;
}
table, th, td {
border: 1px solid darkslategray;
padding: 10px
}
</style>
</head>
<body>
<div style="text-align: center">
<span style="color: darkslategray; font-size: 30px">欢迎光临</span>
<hr/>
<table class="list">
<tr>
<th>偶数行</th>
<th>奇数行</th>
<th>序号index</th>
<th>序号count</th>
<th>ID</th>
<th>姓名</th>
<th>用户名</th>
<th>年龄</th>
<th>性别</th>
<th>生日</th>
<th>备注</th>
<th>操作</th>
</tr>
<!-- user 循环对象 status 循环的序号 -->
<tr th:each="user, status : ${users}" th:object="${user}" th:bgcolor="${status.even}? 'gray'">
<td th:text="${status.even}">true</td>
<td th:text="${status.odd}">true</td>
<td th:text="${status.index}">0</td>
<td th:text="${status.count}">1</td>
<td th:text="${user.id}">1</td>
<td th:text="*{name}">zhangsan</td>
<td th:text="*{userName}">张三</td>
<td th:text="${user.age}">20</td>
<td th:text="${user.sex} == 1 ? '男' : '女'">男</td>
<td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd')}">1980-02-30</td>
<td th:text="${user.note}">1</td>
<td>
<!--<a href="">删除</a>-->
<a th:href="@{delete(id=${user.id}, userName= *{userName})}">删除</a>
<!-- 字符串替换 -->
<a th:href="|/update/${user.id}|">修改</a>
<!-- 字符串拼接 -->
<a th:href="'approve/' + ${user.id}">审核</a>
</td>
</tr>
</table>
</div>
</body>
</html>
5 后台UserController
package com.kaikeba.controller;
import com.kaikeba.entity.pojo.User;
import com.kaikeba.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import java.util.List;
/**
* @author sunyang
* @date 2021/5/31 11:32
*/
@Controller
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/all")
public String searchAll(Model model) {
List<User> all = userService.findAll();
model.addAttribute("users", all);
model.addAttribute("text","我这是测试th:text");
return "user";
}
/**
* @desc thymeleaf测试
*
* @param model
* @return java.lang.String
* @date 2021/5/31 16:42
* @auther sunyang
*/
@GetMapping("/temp")
public String temp(Model model) {
List<User> all = userService.findAll();
model.addAttribute("users", all);
model.addAttribute("text","我这是测试th:text");
return "temp";
}
}
6 实体类User
package com.kaikeba.entity.pojo;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* @author sunyang
* @date 2021/5/31 11:30
*/
@Table(name = "tbs_user")
public class User {
// 主键 自增
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String userName;
private String password;
private String name;
private Integer age;
private Integer sex;
private Date birthday;
private Date created;
private Date updated;
private String note;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
", birthday=" + birthday +
", created=" + created +
", updated=" + updated +
", note='" + note + '\'' +
'}';
}
}
7 UserMapper
package com.kaikeba.mapper;
import com.kaikeba.entity.pojo.User;
import org.apache.ibatis.annotations.Mapper;
/**
* @author sunyang
* @date 2021/5/31 11:29
*/
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User> {
}
8 UserService
package com.kaikeba.service;
import com.kaikeba.entity.pojo.User;
import java.util.List;
/**
* @author sunyang
* @date 2021/5/31 11:29
*/
public interface UserService {
/**
* @return java.util.List<com.kaikeba.entity.pojo.User>
* @desc 查询所有数据
* @date 2021/5/31 11:31
* @auther sunyang
*/
List<User> findAll();
}
9 UserServiceImpl
package com.kaikeba.service.impl;
import com.kaikeba.entity.pojo.User;
import com.kaikeba.mapper.UserMapper;
import com.kaikeba.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @author sunyang
* @date 2021/5/31 11:30
*/
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.selectAll();
}
}
四 测试结果
1 循环
2 布局、内置对象和其他指令
内联的js: