一、基本介绍
Thymeleaf是一个Java库。它是一个XML / XHTML / HTML5模板引擎,能够应用于转换模板文件,以显示您的应用程序产生的数据和文本。它尤其适合于基于XHTML / HTML5的web服务应用程序,同时它可以处理任何XML文件,作为web或独立的应用程序。
Thymeleaf的主要目的是提供一个优雅和格式良好的方式创建模板。为了实现这一目标,它把预定义的逻辑放在XML的标记和属性上,而不是显式放在XML标记的内容上。依靠智能缓存去解析文件,致使其执行期间的I / O操作达到了最少数量,因此其处理的模板的能力实非常快速的。
Thymeleaf的标准方言为:
<span th:text="..."><!--需要引入命名空间-->
<span data-th-text="..."><!--可以不引入命名空间-->
二、语法介绍
1、表达式
Thymeleaf支持的表达式有ognl表达式和springEL表达式。
变量表达式:${...}
<span th:text="${book.author.name}">
消息表达式:#{...}
将key映射成对应的value,也称为文本外部化、国际化或i18n。
<table>
...
<th th:text="#{header.address.city}">...</th>
<th th:text="#{header.address.country}">...</th>
...
</table>
选择表达式:*{...}
它们是在当前选择的对象而不是整个上下文变量映射上执行,效率上会快很多。
<div th:object="${book}">
...
<span th:text="*{title}">...</span><!--取book的title的属性-->
...
</div>
链接表达式:@{...}
链接表达式可以是相对的,在这种情况下,应用程序上下文将不会作为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>
当然,Link表达式可以是绝对的:
<a th:href="@{http://www.mycompany.com/main}">...</a>
分段表达式:th:insert(整个片段插入div)或th:replace(把自己替换掉)
<!--页面一-->
<body>
<div th:fragment="copy" id="copyId"><!--自定义片段,如页面头等-->
...
</div>
<body>
<!--页面二-->
<div th:insert="~{footer :: copy}"></div><!--表示将copy的内容插入到这里来,重用-->
<div th:insert="~{footer :: #copyId}"></div><!--也可以不用fragment,直接根据id-->
2、字面量
文本
<span th:text="${book.author.name}">
布尔
<div th:if="${user.isAdmin()} == false">...
无操作
<span th:text="${user.name}? : _">no user name</span><!--即如果user.name不存在就显示“no user name”-->
3、设置属性值
设置任意属性值 th:attr:
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<input type="submit" th:attr="value=#{subscribe.submit}"/>
</form>
设置指定属性:
<form action="subscribe.html" th:action="@{/subscribe}">
<input type="submit" th:value="#{subscribe.submit}"/>
</form>
4、迭代器
基本的迭代 th:each :
<li th:each="book : ${books}" th:text = "${book.title}">En las del Sar</li>
状态变量:index、count、size、current、even(奇数)/odd(偶数)、first、last
下面是一个例子
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</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>
</table>
5、条件语句
if/unless
Thymeleaf中使用th:if和th:unless属性进行条件判断,下面的例子中,标签只有在th:if中条件成立时才显示:
<a th:href="@{/login}" th:if=${session.user != null}>Login</a>
th:unless于th:if恰好相反,只有表达式中的条件不成立,才会显示其内容。
Switch
Thymeleaf同样支持多路选择Switch结构:
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
</div>
默认属性default可以用*表示:
<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>
6、注释
解析器级别注释:静态时候会被显示,当模板执行(Thymeleaf解析)时,这些会被注释
<!--/*-->
<div>
you can see me !
</div>
<!--*/-->
原型注释:当模板静态打开时不显示,当模板执行(Thymeleaf解析)时,这些被注释内容将会显示,和上面相反:
<span>hello</span>
<!--/*/
<div th:text="${...}">
...
</div>
/*/-->
<span>goodbye</span>
7、内联
[[...]]和[(...)]分别对应于th:text和th:utext
<p>the message is [(${msg})]</p>
===》
<p>the message is <b>great</b></p>
<p>the message is [[${msg}]]</p>
===》
<p>the message is < b > great < /b ></p>
8、内置对象
1.基本对象
ctx,对象继承org.thymeleaf.context.IContext或者org.thymeleaf.context.IWebContext,取决于当前环境是不是web环境。如果程序集成了spring,那么将会是org.thymeleaf.spring[3|4].context.SpringWebContext。
/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.IContext
* ======================================================================
*/
${#ctx.locale}
${#ctx.variables}
/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.IWebContext
* ======================================================================
*/
${#ctx.applicationAttributes}
${#ctx.httpServletRequest}
${#ctx.httpServletResponse}
${#ctx.httpSession}
${#ctx.requestAttributes}
${#ctx.requestParameters}
${#ctx.servletContext}
${#ctx.sessionAttributes}
locale,java.util.Locale对象的访问.
vars,org.thymeleaf.context的实例。访问VariablesMap所有上下文中的变量(包含本笃和ctx.variables中的)。
/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.VariablesMap
* ======================================================================
*/
${#vars.get('foo')}
${#vars.containsKey('foo')}
${#vars.size()}
2.web环境中访问request/session等属性
当在web环境中使用Thymeleaf,我们可以使用一系列的快捷方式访问请求的参数,会话和应用程序的属性.
param,获取请求的参数.
/*
* ============================================================================
* 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,获取应用程序/ servlet上下文属性。
/*
* =============================================================================
* See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap
* =============================================================================
*/
${application.foo} // Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}
3.web环境对象
httpServletRequest :javax.servlet.http.HttpServletRequest对象实例。
${#httpServletRequest.getAttribute('foo')}
${#httpServletRequest.getParameter('foo')}
${#httpServletRequest.getContextPath()}
${#httpServletRequest.getRequestName()}
...
httpSession:javax.servlet.http.HttpSession实例。
${#httpSession.getAttribute('foo')}
${#httpSession.id}
${#httpSession.lastAccessedTime}
...
4.spring环境对象
themes : 提供和“ spring:theme JSP tag.”同样的功能。
${#themes.code('foo')}
<!--直接访问spring注册对象-->
<div th:text="${@authService.getUserName()}">...</div>
三、项目实战
1、基本搭建
我们在这里创建一个包含用户增删改查功能的项目,这里采用Gradle来构建项目,其中build.gradle配置如下:
// buildscript 代码块中脚本优先执行
buildscript {
// ext 用于定义动态属性
ext {
springBootVersion = '1.5.2.RELEASE'
}
// 自定义 Thymeleaf 和 Thymeleaf Layout Dialect 的版本
ext['thymeleaf.version'] = '3.0.3.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.0'
// 使用了 Maven 的中央仓库(你也可以指定其他仓库)
repositories {
//mavenCentral()
maven {
url 'http://maven.aliyun.com/nexus/content/groups/public/'
}
}
// 依赖关系
dependencies {
// classpath 声明说明了在执行其余的脚本时,ClassLoader 可以使用这些依赖项
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
// 使用插件
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
// 打包的类型为 jar,并指定了生成的打包的文件名称和版本
jar {
baseName = 'thymeleaf-in-action'
version = '1.0.0'
}
// 指定编译 .java 文件的 JDK 版本
sourceCompatibility = 1.8
// 默认使用了 Maven 的中央仓库。这里改用自定义的镜像库
repositories {
//mavenCentral()
maven {
url 'http://maven.aliyun.com/nexus/content/groups/public/'
}
}
// 依赖关系
dependencies {
// 该依赖对于编译发行是必须的
compile('org.springframework.boot:spring-boot-starter-web')
// 添加 Thymeleaf 的依赖
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
// 该依赖对于编译测试是必须的,默认包含编译产品依赖和编译时依
testCompile('org.springframework.boot:spring-boot-starter-test')
}
然后配置application.properties内容如下:
# THYMELEAF
spring.thymeleaf.encoding=UTF-8
# 热部署静态文件
spring.thymeleaf.cache=false
# 使用HTML5标准
spring.thymeleaf.mode=HTML5
2、后台逻辑
1.实体
public class User {
private long id; // 用户的唯一标识
private String name;
private int age;
}
2.业务逻辑
/**
* 用户仓库.
*/
public interface UserRepository {
/**
* 新增或者修改用户
* @param user
* @return
*/
User saveOrUpateUser(User user);
/**
* 删除用户
* @param id
*/
void deleteUser(Long id);
/**
* 根据用户id获取用户
* @param id
* @return
*/
User getUserById(Long id);
/**
* 获取所有用户的列表
* @return
*/
List<User> listUser();
}
其中实现类如下:
@Repository
public class UserRepositoryImpl implements UserRepository {
private static AtomicLong counter = new AtomicLong();
private final ConcurrentMap<Long, User> userMap = new ConcurrentHashMap<Long, User>();
public UserRepositoryImpl(){
User user = new User();
user.setAge(30);
user.setName("Way Lau");
this.saveOrUpateUser(user);
}
@Override
public User saveOrUpateUser(User user) {
Long id = user.getId();
if (id <= 0) {
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 new ArrayList<User>(this.userMap.values());
}
}
3.控制层
/**
* 用户控制器.
*/
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository userRepository;
/**
* 从 用户存储库 获取用户列表
* @return
*/
private List<User> getUserlist() {
return userRepository.listUser();
}
/**
* 查询所用用户
* @return
*/
@GetMapping
public ModelAndView list(Model model) {
model.addAttribute("userList", getUserlist());
model.addAttribute("title", "用户管理");
return new ModelAndView("users/list", "userModel", model);
}
/**
* 根据id查询用户
* @param message
* @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 user
* @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
* @param result
* @param redirect
* @return
*/
@PostMapping
public ModelAndView create(User user) {
user = userRepository.saveOrUpateUser(user);
return new ModelAndView("redirect:/users");
}
/**
* 删除用户
* @param id
* @return
*/
@GetMapping(value = "delete/{id}")
public ModelAndView delete(@PathVariable("id") Long id, Model model) {
userRepository.deleteUser(id);
model.addAttribute("userList", getUserlist());
model.addAttribute("title", "删除用户");
return new ModelAndView("users/list", "userModel", model);
}
/**
* 修改用户
* @param user
* @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);
}
}
3、前台页面
1.公共页脚
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>
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://waylau.com">Welcome to waylau.com</a>
</div>
</body>
</html>
2.功能页面
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 waylau.com</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 waylau.com</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 waylau.com</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>
下面是项目结构图:
4、扩展
1.异步加载
在Thymeleaf中进行页面分页操作时,在翻页操作中,我们显然要通过异步方式加载数据,下面我们一分页操作来看其异步加载操作,首先其html页面如下:
我们对应的js代码为
然后后台代码如下:
最后附上分页的脚步如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<nav data-th-fragment="page" data-th-if="${page.totalPages gt 0}" data-th-object="${page}">
<!-- 处理页数小于等于7 的情况 -->
<ul class="pagination" data-th-if="${page.totalPages le 7}" >
<!-- 总记录数 -->
<li class="tbpage-total-elements disabled">共[[${page.totalElements}]]条</li>
<!-- 页面大小 -->
<select class="custom-select tbpage-size" data-th-attr="pageIndex=${page.number}">
<option data-th-each="i : ${#arrays.toIntegerArray({5,10,40,100})}" data-th-value="${i}"
data-th-selected="${i eq page.size}" data-th-text="${i}"></option>
</select>
<!-- 上一页 -->
<li class="page-item" data-th-classappend="*{first} ? 'disabled' : ''">
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.number} - 1" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 迭代生成页码 -->
<li class="page-item" data-th-each="i : ${#numbers.sequence(1, page.totalPages)}"
data-th-classappend="${(page.number + 1) eq i} ? 'active' : ''" >
<a class="page-link tbpage-item" data-th-attr="pageIndex=${i} - 1" href="javascript:void(0);">
<span data-th-text="${i}"></span>
</a>
</li>
<!-- 下一页 -->
<li class="page-item" data-th-classappend="*{last} ? 'disabled' : ''">
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.number} + 1" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
<!-- 处理页数大于7 的情况 -->
<ul class="pagination" data-th-if="${page.totalPages gt 7}" >
<!-- 总记录数 -->
<li class="tbpage-total-elements disabled">共[[${page.totalElements}]]条</li>
<!-- 页面大小 -->
<select class="custom-select tbpage-size" data-th-attr="pageIndex=${page.number}">
<option data-th-each="i : ${#arrays.toIntegerArray({5,10,40,100})}" data-th-value="${i}"
data-th-selected="${i eq page.size}" data-th-text="${i}"></option>
</select>
<!-- 上一页 -->
<li class="page-item" data-th-classappend="*{first} ? 'disabled' : ''">
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.number} - 1" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 首页 -->
<li class="page-item" data-th-classappend="${(page.number + 1) eq 1} ? 'active' : ''" >
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=0">1</a>
</li>
<!-- 当前页面小于等于4 -->
<li class="page-item" data-th-if="${(page.number + 1) le 4}" data-th-each="i : ${#numbers.sequence(2,5)}"
data-th-classappend="${(page.number + 1) eq i} ? 'active' : ''" >
<a class="page-link tbpage-item" href="javascript:void(0);" data-th-attr="pageIndex=${i} - 1">
<span data-th-text="${i}"></span>
</a>
</li>
<li class="page-item disabled" data-th-if="${(page.number + 1) le 4}">
<a href="javascript:void(0);" class="page-link tbpage-item">
<span aria-hidden="true">...</span>
</a>
</li>
<!-- 最后一页与当前页面之差,小于等于3 -->
<li class="page-item disabled" data-th-if="${(page.totalPages-(page.number + 1)) le 3}">
<a href="javascript:void(0);" class="page-link tbpage-item">
<span aria-hidden="true">...</span>
</a>
</li>
<li class="page-item" data-th-if="${(page.totalPages-(page.number + 1)) le 3}" data-th-each="i : ${#numbers.sequence(page.totalPages-4, page.totalPages-1)}"
data-th-classappend="${(page.number + 1) eq i} ? 'active' : ''" >
<a class="page-link tbpage-item" href="javascript:void(0);" data-th-attr="pageIndex=${i} - 1">
<span data-th-text="${i}"></span>
</a>
</li>
<!-- 最后一页与当前页面之差大于3,且 当前页面大于4-->
<li class="page-item disabled" data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">
<a href="javascript:void(0);" class="page-link tbpage-item">
<span aria-hidden="true">...</span>
</a>
</li>
<li class="page-item" data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}" >
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.number}">[[${page.number}]]</a>
</li>
<li class="page-item active" data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.number} + 1">[[${page.number + 1}]]</a>
</li>
<li class="page-item" data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.number} + 2">[[${page.number + 2}]]</a>
</li>
<li class="page-item disabled" data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">
<a href="javascript:void(0);" class="page-link tbpage-item">
<span aria-hidden="true">...</span>
</a>
</li>
<!-- 最后一页 -->
<li class="page-item" data-th-classappend="${(page.number + 1) eq page.totalPages} ? 'active' : ''" >
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.totalPages} - 1">[[${page.totalPages}]]</a>
</li>
<!-- 下一页 -->
<li class="page-item" data-th-classappend="*{last} ? 'disabled' : ''">
<a href="javascript:void(0);" class="page-link tbpage-item" data-th-attr="pageIndex=${page.number} + 1" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</body>
</html>