Thymeleaf
官方文档
在线文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
离线文档:usingthymeleaf.pdf
基本介绍
Thymeleaf 是什么
- Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,可完全替代 JSP。
- Thymeleaf 是一个 java 类库,它是一个 xml/xhtml/html5 的模板引擎,可以作为 mvc 的 web 应用的 view 层。
Thymeleaf 的优点
- 实现 JSTL、 OGNL 表达式效果, 语法相似,Java 程序员上手快。
- Thymeleaf 模版页面无需服务器渲染,也可以被浏览器运行,页面简洁。
- SpringBoot 支持 FreeMarker、Thymeleaf、veocity。
Thymeleaf 的缺点
Thymeleaf: Thymeleaf is a modern server-side Java template engine for both web and standalone environments。
缺点:并不是一个高性能的引擎,适用于单体应用。
如果要做一个高并发的应用,选择前后端分离更好。
Thymeleaf 机制说明
- Thymeleaf 是服务器渲染技术,页面数据是在服务端进行渲染的。
- 比如: manage.html 中一段 thymeleaf 代码,是在用户请求该页面时,有 thymeleaf 模板 引擎完成处理的(在服务端完成),并将结果页面返回。
【占位】
- 因此使用了 Thymeleaf , 并不是前后端分离。
Thymeleaf 语法
表达式
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | $ | 获取请求域、session 域、对象等值 |
选择变量 | * | 获取上下文对象值 |
消息 | # | 获取国际化等值 |
链接 | @ | 生成链接 |
片段表达式 | ~ | jsp:include 作用,引入公共页面片段 |
字面量
文本值:'liang' , 'hello' ,… 数字:10 , 7 , 36.8 ,… 布尔值:true , false
空值:null
变量:name,age,.... 变量不能有空格
文本操作
字符串拼接:+
变量替换: |age= ${age}|
运算符
数学运算
运算符: + , - , * , / , %
布尔运算
运算符: and , or
一元运算: ! , not
比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
th属性
html 有的属性,Thymeleaf 基本都有,而常用的属性大概有七八个。其中 th 属性执行的优 先级从 1~8,数字越低优先级越高。
● th:text :设置当前元素的文本内容,相同功能的还有 th:utext,两者的区别在于前者不会转义 html 标签,后者会。优先级不高:order=7
● th:value:设置当前元素的 value 值,类似修改指定属性的还有 th:src,th:href。优先 级不高:order=6
● th:each:遍历循环元素,和 th:text 或 th:value 一起使用。注意该属性修饰的标签位置,详细往后看。优先级很高:order=2
● th:if:条件判断,类似的还有 th:unless,th:switch,th:case。优先级较高:order=3
● th:insert:代码块引入,类似的还有 th:replace,th:include,三者的区别较大,若使用不恰当会破坏 html 结构,常用于公共代码块提取的场景。优先级最高:order=1
● th:fragment:定义代码块,方便被 th:insert 引用。优先级最低:order=8
● th:object:声明变量,一般和*{}一起配合使用,达到偷懒的效果。优先级一般:order=4
● th:attr:修改任意属性,实际开发中用的较少,因为有丰富的其他 th 属性帮忙,类似的还有 th:attrappend,th:attrprepend。优先级一般:order=5
迭代
<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>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<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>
条件运算
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<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>
使用 Thymeleaf -th 属性需要注意点
- 若要使用 Thymeleaf 语法,首先要声明名称空间:
xmlns:th="http://www.thymeleaf.org"
- 设置文本内容 th:text,设置 input 的值 th:value,循环输出 th:each,条件判断 th:if, 插入代码块 th:insert,定义代码块 th:fragment,声明变量 th:object
- th:each 的用法需要格外注意,打个比方:如果你要循环一个 div 中的 p 标签,则 th:each 属性必须放在 p 标签上。若你将 th:each 属性放在 div 上,则循环的是将整个 div。
- 变量表达式中提供了很多的内置方法,该内置方法是用
#
开头,请不要与#{}
消息表达式弄混。
应用实例
需求说明
使用 SpringBoot + Thymeleaf 完成简单的用户登录-列表功能。

代码实现
创建Maven项目,springboot-usersys。
修改 pom.xml
<!--导入springboot父工程-规定写法-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--引入thymeleaf-start: 会进行默认配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
引入 starter-Thymeleaf,项目会自动完成配置,程序员按照规则开发即可。

创建 adminLogin.html 和静态图片到指定目录
注意:将 html 文件放到 templates/ 目录下,该目录不能直接访问

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
<h1>用户登陆</h1>
<form action="#"method="post">
<label style="color: red"></label><br/>
用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
密 码:<input type="password" style="width:150px" name="password"/><br/><br/>
<input type="submit" value="登录"/>
<input type="reset" value="重新填写"/>
</form>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
创建 src/main/java/com/lzw/springboot/Application.java
package com.lzw.springboot;
import org.springframework.boot.SpringApplication;
/**
* @author LiAng
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
创建 src/main/java/com/lzw/springboot/controller/IndexController.java
package com.lzw.springboot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author LiAng
*/
@Controller
public class IndexController {
//编写方法,转发到登录页面
@GetMapping(value = {"/", "/login"})
public String login(){
/**
* 1. 因为我们引入了starter-thymeleaf
* 2. 这里就会直接使用视图解析到 thymeleaf下的模板文件adminLogin.html
*/
return "adminLogin";
}
}
启动项目

创建 src/main/java/com/lzw/springboot/bean/Admin.java
package com.lzw.springboot.bean;
import lombok.Data;
/**
* @author LiAng
*/
@Data
public class Admin {
private String name;
private String password;
}
创建 src/main/java/com/lzw/springboot/bean/User.java
package com.lzw.springboot.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author LiAng
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String password;
private Integer age;
private String email;
}
创建 resources/templates/manage.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>管理后台</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<a href='#'>返回管理界面</a> <a href='#'>安全退出</a> 欢迎您:xxx
<hr/>
<div style="text-align: center">
<h1>管理雇员~</h1>
<table border="1px" cellspacing="0" bordercolor="green" style="width:800px;margin: auto">
<tr bgcolor="pink">
<td>id</td>
<td>name</td>
<td>pwd</td>
<td>email</td>
<td>age</td>
</tr>
<tr bgcolor="#ffc0cb">
<td>a</td>
<td>b</td>
<td>c</td>
<td>d</td>
<td>e</td>
</tr>
</table>
<br/>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
创建 src/main/java/com/lzw/springboot/controller/AdminController.java
package com.lzw.springboot.controller;
import com.lzw.springboot.bean.Admin;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpSession;
/**
* @author LiAng
*/
@Controller
public class AdminController {
//响应用户的登录请求
@PostMapping("/login")
public String login(Admin admin, HttpSession session, Model model){
//验证用户是否合法
if(StringUtils.hasText(admin.getName()) && "666".equals(admin.getPassword())){
//合法,重定向到manage.html
//java web, 不使用请求转发是防止刷新页面会重复提交
//这里为什么写的是 manage.html, 因为这样可以更加明确的表示到哪个页面
//manage.html表示要去找 方法的映射路径为 manage.html,而不是直接去找html
return "redirect:/manage.html";
}else{
//重新登陆
return "adminLogin";
}
}
//处理用户的请求到 manage.html
@GetMapping("/manage.html")
public String mainPage(Model model, HttpSession session) {
//这里用集合-模拟用户数据, 放入到request域中,并显示
return "manage"; //这里才是我们的视图解析到 /templates/manage.html
}
}
启动项目


修改 AdminController.java
//处理用户的请求到 manage.html
@GetMapping("/manage.html")
public String mainPage(Model model, HttpSession session) {
//这里用集合-模拟用户数据, 放入到request域中,并显示
ArrayList<User> users = new ArrayList<>();
users.add(new User(1, "关羽~", "666666", 20, "gy@sohu.com"));
users.add(new User(2, "张飞", "666666", 30, "zf@sohu.com"));
users.add(new User(3, "赵云", "666666", 22, "zy@sohu.com"));
users.add(new User(4, "马超", "666666", 28, "mc@sohu.com"));
users.add(new User(5, "黄忠", "666666", 50, "hz@sohu.com"));
//将数据放入到request域
model.addAttribute("users", users);
return "manage"; //这里才是我们的视图解析到 /templates/manage.html
}
修改 manage.html
<table border="1px" cellspacing="0" bordercolor="green" style="width:800px;margin: auto">
<tr bgcolor="pink">
<td>id</td>
<td>name</td>
<td>pwd</td>
<td>email</td>
<td>age</td>
</tr>
<tr bgcolor="#ffc0cb" th:each="user:${users}">
<td th:text="${user.id}">a</td>
<td th:text="${user.name}">b</td>
<td th:text="${user.password}">c</td>
<td th:text="${user.email}">d</td>
<td th:text="${user.age}">e</td>
</tr>
</table>
启动项目

修改 AdminController.java
//响应用户的登录请求
@PostMapping("/login")
public String login(Admin admin, HttpSession session, Model model){
//验证用户是否合法
if(StringUtils.hasText(admin.getName()) && "666".equals(admin.getPassword())){
//合法,重定向到manage.html
//java web, 不使用请求转发是防止刷新页面会重复提交
//这里为什么写的是 manage.html, 因为这样可以更加明确的表示到哪个页面
//manage.html表示要去找 方法的映射路径为 manage.html,而不是直接去找html
return "redirect:/manage.html";
}else{
//重新登陆
model.addAttribute("msg" ,"用户名/密码错误");
return "adminLogin";
}
}
修改 adminLogin.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
<h1>用户登陆</h1>
<form action="#" th:action="@{/login}" method="post">
<label style="color: red" th:text="${msg}"></label><br/>
用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
密 码:<input type="password" style="width:150px" name="password"/><br/><br/>
<input type="submit" value="登录"/>
<input type="reset" value="重新填写"/>
</form>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>


处理非法登录
修改 AdminController.java
package com.lzw.springboot.controller;
import com.lzw.springboot.bean.Admin;
import com.lzw.springboot.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
/**
* @author LiAng
*/
@Controller
public class AdminController {
//响应用户的登录请求
@PostMapping("/login")
public String login(Admin admin, HttpSession session, Model model){
//验证用户是否合法
if(StringUtils.hasText(admin.getName()) && "666".equals(admin.getPassword())){
//将登录用户保存到session
session.setAttribute("loginAdmin", admin);
//合法,重定向到manage.html
//java web, 不使用请求转发是防止刷新页面会重复提交
//这里为什么写的是 manage.html, 因为这样可以更加明确的表示到哪个页面
//manage.html表示要去找 方法的映射路径为 manage.html,而不是直接去找html
return "redirect:/manage.html";
}else{
//重新登陆
model.addAttribute("msg" ,"用户名/密码错误");
return "adminLogin";
}
}
//处理用户的请求到 manage.html
@GetMapping("/manage.html")
public String mainPage(Model model, HttpSession session) {
Object loginAdmin = session.getAttribute("loginAdmin");
if(null != loginAdmin){
//说明成功登录过
//这里用集合-模拟用户数据, 放入到request域中,并显示
ArrayList<User> users = new ArrayList<>();
users.add(new User(1, "关羽~", "666666", 20, "gy@sohu.com"));
users.add(new User(2, "张飞", "666666", 30, "zf@sohu.com"));
users.add(new User(3, "赵云", "666666", 22, "zy@sohu.com"));
users.add(new User(4, "马超", "666666", 28, "mc@sohu.com"));
users.add(new User(5, "黄忠", "666666", 50, "hz@sohu.com"));
//将数据放入到request域
model.addAttribute("users", users);
return "manage"; //这里才是我们的视图解析到 /templates/manage.html
}else{
//这里就返回登录页面
model.addAttribute("msg","你还没有登录");
return "adminLogin";//请求转发到adminLogin.html
}
}
}

修改 manage.html
<a href='#'>返回管理界面</a> <a href='#' th:href="@{/}">安全退出</a> 欢迎您:[[${session.loginAdmin.name}]]
