1. 简介
之前一直使用Freemarker,对Thymeleaf了解但是不熟悉,最近因为其他项目组他们要快速搭建后台,使用了一个三方的框架用到了Thymeleaf,所以进一步了解了一些。
发现Thymeleaf更加像前端的模板语言,所以对静态页面有更好的兼容性,就是,如果是Freemarker模板文件,浏览器是解析不了的,会直接出错。而使用Thymeleaf模板文件,如果没有解析,浏览器也能解析,基本样式基本没有问题,只是不能解析数据。
因为Thymeleaf使用属性,少了很多像Freemarker的tag,这样语法上看起来也更加简介一些。
这篇文章主要记录一下Thymeleaf中一些常用的功能,以便于看到这些东西知道是什么意思。
2. 开始
在SpringBoot中使用Thymeleaf非常简单,只需要添加下面的依赖就可以了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
然后,基本就和使用JSP一样,除了语法不同。
SpringBoot Thymeleaf默认配置会去classpath的templates去找模板文件,和Freemarker默认的后缀ftl不同,Thymeleaf直接使用的html后缀,就是为了就算不能解析,浏览器也能识别。
接下来添加一个Controller:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/data")
public class DataController {
@RequestMapping("/index")
public String show(Model model){
model.addAttribute("id","10001");
model.addAttribute("name","Tim");
model.addAttribute("userhome","<a style='color:red' href='/user/10001'>Tim</a>");
return "index";
}
}
然后添加一个index.html文件:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Index</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<div th:text="'用户ID:' + ${id}"/>
<div th:text="'用户名称:' + ${name}"/>
<div th:utext="'用户主页:' + ${userhome}"/>
</body>
</html>
如果你对模板文件熟悉,不管是前端模板,还是后端模板,基本一看就能大致猜出上面的模板是什么意思。
Thymeleaf通过th:text来绑定标签的文本属性,就是给标签设置text内容。其中th是Thymeleaf的缩写,text表示文本属性。
除了使用th:text,还可以使用th:utext,th:utext最重要的一点是可以设置带html标签的text
3. list遍历
@RequestMapping("/list")
public String list(Model model) {
List<Integer> ids = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ids.add(i) ;
}
model.addAttribute("ids", ids);
return "collection/list";
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>List</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<div>
<table>
<tr><td>编号</td><td>ID</td><td>是否是第偶数个元素</td><td>是否是第奇数个元素</td></tr>
<tr th:each="id,memberStat:${ids}">
<td th:text="${memberStat.index + 1}"/>
<td th:text="${id}"/>
<td th:text="${memberStat.even}"/>
<td th:text="${memberStat.odd}"/>
</tr>
</table>
</div>
</body>
</html>
4. map遍历
@RequestMapping("/map")
public String map(Model model) {
Map<Integer,String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.put(i,"VL" + i);
}
model.addAttribute("map", map);
return "collection/map";
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Map</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<div>
<table>
<tr><td>编号</td><td>key</td><td>value</td><td>是否是第偶数个元素</td><td>是否是第奇数个元素</td></tr>
<tr th:each="entry,memberStat:${map}">
<td th:text="${memberStat.index + 1}"/>
<td th:text="${entry.key}"/>
<td th:text="${entry.value}"/>
<td th:text="${memberStat.even}"/>
<td th:text="${memberStat.odd}"/>
</tr>
</table>
</div>
</body>
</html>
5. if unless switch
@RequestMapping("/ifel")
public String ifel(Model model) {
model.addAttribute("score", 89);
return "tool/ifel";
}
<body>
<div>
<div th:if="${score ge 90}">
A
</div>
<div th:if="${score lt 90 && score ge 80}">
B
</div>
<div th:unless="${score ge 80}">
C
</div>
</div>
<div>
<div th:switch="${score}">
<div th:case="100">100</div>
<div th:case="99">90</div>
<div th:case="*">都不匹配</div>
</div>
</div>
</body>
</html>
if是条件满足执行,unless不是else的意思,而是条件不满足执行。
Thymeleaf支持switch,这样就避免了写多个if语句了。
6. @
用过JSP的朋友都知道,JSP的路径真是一点都不方便,要自己去拼接,虽然也可以直接添加拦截器、工具类去处理。
Thymeleaf提供了@,然我们不用去拼接网站前缀,主要是处理上下文部分。
<script type="text/javascript" th:src="@{/js/jquery.js}"></script>
<a th:href="@{/user/info}">访问controller方法</a>
7. th:object
th:object提供了一种省略对象前缀的访问方式,看实例就清楚。
@RequestMapping("/info")
public String userInfo(Model model) {
User user = new User();
user.setId(10001);
user.setName("轩辕雪");
user.setAge(25);
model.addAttribute("user", user);
return "user/info";
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户信息</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<div>
<div th:text="'用户ID:' + ${user.id}"/>
<div th:text="'用户姓名:' + ${user.name}"/>
<div th:text="'用户年龄:' + ${user.age}"/>
</div>
<div th:object="${user}">
<div th:text="'用户ID:' + *{id}"/>
<div th:text="'用户姓名:' + *{name}"/>
<div th:text="'用户年龄:' + *{age}"/>
</div>
</body>
</html>
上面的两种绑定方式是完全等价的。
8. 工具方法
@RequestMapping("/tool")
public String tool(Model model) {
Set<String> set = new HashSet<>() ;
set.add("A");
Map<String,Integer> map = new HashMap<>();
map.put("a",1);
List<Integer> list = new ArrayList<>() ;
for (int i = 0 ; i < 10 ; i ++) {
list.add(i) ;
}
Integer[] array = new Integer[list.size()];
list.toArray(array);
model.addAttribute("list", list) ;
model.addAttribute("set", set) ;
model.addAttribute("map", map) ;
model.addAttribute("array", array) ;
model.addAttribute("now", new Date()) ;
return "tool/tool" ;
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Index</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<div>日期:</div>
<div>
<div th:text="${#dates.millisecond(now)}"/>
<div th:text="${#dates.format(now,'yyyy-MM-dd')}"/>
<div th:text="${#dates.format(now,'yyyy-MM-dd HH:mm:ss')}"/>
</div>
<div>字符串:</div>
<div>
<div th:text="${#strings.isEmpty('')}"/>
<div th:text="${#strings.replace('$199','$','¥')}"/>
<div th:text="${#strings.toUpperCase('love')}"/>
<div th:text="${#strings.trim(' I Love You ')}"/>
</div>
<div>其他:</div>
<div>
<div th:text="${#sets.contains(set,'a')}"/>
<div th:text="${#lists.size(list)}"/>
<div th:text="${#maps.containsKey(map, 'a')}"/>
<div th:text="${#arrays.length(array)}"/>
<div th:text="${#aggregates.sum(array)}"/>
<div th:text="${#numbers.formatInteger(3.1415926,3,'POINT')}"/>
<div th:text="${#numbers.formatDecimal(3.1415926,3,'POINT',2,'COMMA')}"/>
<div th:text="${#numbers.formatPercent(3.1415926, 3, 2)}"/>
</div>
</body>
</html>
注意dates的millisecond只是获取毫秒,而不是时间戳。
这样的工具类方法还有一大堆,具体可以参考官方的:Thymeleaf工具类
9. include replace insert
在处理页面的时候,经常需要引入一些公共页面,Thymeleaf中我们可以使用include实现。
和JSP、Freemarker不同,Thymeleaf可以不导入整个页面,而是只导入这个页面中的某些片段(fragment)
在页面中,我们可以通过下面的方式来定义片段:
<div th:fragment="page"></div>
通过下面的方式引入片段:
<div th:include="pagefile::page"></div>
include的有一些简写方式:
<!-- "::"前面是模板文件名,后面是fragement的名称 -->
<div th:include="template/pagefile::page"></div>
<!-- 省略模板文件名,只有fragment名称,则加载本页面对应的fragment -->
<div th:include="::page"></div>
<!-- 只写模板文件名,省略fragment名称,则加载整个页面 -->
<div th:include="template/nav"></div>
来一个示例看一看,加强理解:
@RequestMapping("/myinclude")
public String myinclude(Model model) {
return "user/myinclude";
}
定义一个公共的包含文件include.html:
<head th:fragment=header(title)>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="keywords" content="">
<meta name="description" content="">
<title th:text="${title}"></title>
<link rel="shortcut icon" href="favicon.ico">
</head>
<div th:fragment="footer">
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
</div>
<div clss="fragment-content-class" th:fragment="content(first,second)">
<div th:text="'第一个参数:' + ${first} + ' 第二个参数:' + ${second}"></div>
</div>
导入页面:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="common/include :: header('myinclude-title')" />
</head>
<body>
<div class="page-content-class" th:insert="common/include :: content('Hello','insert')"></div>
<div class="page-content-class" th:include="common/include :: content('Hello','include')"></div>
<div class="page-content-class" th:replace="common/include :: content('Hello','replace')"></div>
<th:block th:insert="common/include :: footer" />
</body>
</html>
Thymeleaf渲染之后的页面:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="keywords" content="">
<meta name="description" content="">
<title>myinclude-title</title>
<link rel="shortcut icon" href="favicon.ico">
</head>
<body>
<div class="page-content-class"><div clss="fragment-content-class">
<div>第一个参数:Hello 第二个参数:insert</div>
</div></div>
<div class="page-content-class">
<div>第一个参数:Hello 第二个参数:include</div>
</div>
<div clss="fragment-content-class">
<div>第一个参数:Hello 第二个参数:replace</div>
</div>
<div>
<script src="https://my.oschina.net/js/jquery.min.js"></script>
<script src="https://my.oschina.net/js/bootstrap.min.js"></script>
</div>
</body>
</html>
我们可以简单的小结一下:
- th:insert:保留自己和th:fragment的主标签
- th:replace:只保留th:fragment的主标签
- th:include:只保留自己的主标签
10. 处理本地文件
除了在Web中使用,我们也当做一个本地工具使用:
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import java.util.HashMap;
import java.util.Map;
public class ThymeleafHelper {
private static TemplateEngine templateEngine;
static {
templateEngine = new TemplateEngine();
FileTemplateResolver resolver = new FileTemplateResolver();
resolver.setPrefix("G:\\tmp\\thymeleaf\\");
resolver.setCharacterEncoding("UTF-8");
resolver.setSuffix(".html");
templateEngine.setTemplateResolver(resolver);
}
public static String redering(String templateFileName, Map<String,Object> data) {
Context context = new Context();
context.setVariables(data);
return templateEngine.process(templateFileName, context);
}
public static void main(String[] args) {
HashMap<String, Object> data = new HashMap<>();
data.put("id","10001");
data.put("name","Tim");
data.put("userhome","<a style='color:red' href='/user/10001'>Tim</a>");
System.out.println(redering("index",data));
}
}
可能需要单独引入ognl包:
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.2.18</version>
</dependency>