共计 12801 个字符,预计需要花费 33 分钟才能阅读完成。
@[toc]
尽管当初风行前后端拆散,然而后端模版在一些要害中央还是十分有用的,例如邮件模版、代码模版等。当然也不排除一些古老的我的项目后端仍然应用动静模版。
Thymeleaf 简洁丑陋、容易了解,并且完满反对 HTML5,能够间接关上动态页面,同时不新增标签,只需加强属性,这样也升高了学习老本。
因而松哥明天花点工夫和大家认真分享一下 Thymeleaf。
1. Thymeleaf 简介
Thymeleaf 是新一代 Java 模板引擎,它相似于 Velocity、FreeMarker 等传统 Java 模板引擎,然而与传统 Java 模板引擎不同的是,Thymeleaf 反对 HTML 原型。
它既能够让前端工程师在浏览器中间接关上查看款式,也能够让后端工程师联合实在数据查看显示成果,同时,SpringBoot 提供了 Thymeleaf 自动化配置解决方案,因而在 SpringBoot 中应用 Thymeleaf 十分不便。
事实上,Thymeleaf 除了展现根本的 HTML,进行页面渲染之外,也能够作为一个 HTML 片段进行渲染,例如咱们在做邮件发送时,能够应用 Thymeleaf 作为邮件发送模板。
另外,因为 Thymeleaf 模板后缀为 .html
,能够间接被浏览器关上,因而,预览时十分不便。
2. 整合 Spring Boot
2.1 根本用法
Spring Boot 中整合 Thymeleaf 非常容易,只须要创立我的项目时增加 Thymeleaf 即可:
创立实现后,pom.xml 依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
当然,Thymeleaf 不仅仅能在 Spring Boot 中应用,也能够应用在其余中央,只不过 Spring Boot 针对 Thymeleaf 提供了一整套的自动化配置计划,这一套配置类的属性在 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties
中,局部源码如下:
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = DEFAULT_PREFIX;
private String suffix = DEFAULT_SUFFIX;
private String mode = "HTML";
private Charset encoding = DEFAULT_ENCODING;
private boolean cache = true;
//...
}
- 首先通过
@ConfigurationProperties
注解,将application.properties
前缀为spring.thymeleaf
的配置和这个类中的属性绑定。 - 前三个
static
变量定义了默认的编码格局、视图解析器的前缀、后缀等。 - 从前三行配置中,能够看进去,
Thymeleaf
模板的默认地位在resources/templates
目录下,默认的后缀是html
。 - 这些配置,如果开发者不本人提供,则应用 默认的,如果本人提供,则在
application.properties
中以spring.thymeleaf
开始相干的配置。
而咱们刚刚提到的,Spring Boot 为 Thymeleaf 提供的自动化配置类,则是 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
,局部源码如下:
@Configuration
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
public class ThymeleafAutoConfiguration {}
能够看到,在这个自动化配置类中,首先导入 ThymeleafProperties
,而后 @ConditionalOnClass
注解示意当以后零碎中存在 TemplateMode
和 SpringTemplateEngine
类时,以后的自动化配置类才会失效,即只有我的项目中引入了 Thymeleaf
相干的依赖,这个配置就会失效。
这些默认的配置咱们简直不须要做任何更改就能够间接应用了。如果开发者有非凡需要,则能够在 application.properties 中配置以 spring.thymeleaf 结尾的属性即可。
接下来咱们就能够创立 Controller 了,实际上引入 Thymeleaf 依赖之后,咱们能够不做任何配置。新建的 IndexController 如下:
@Controller
public class IndexController {@GetMapping("/index")
public String index(Model model) {List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {User u = new User();
u.setId((long) i);
u.setName("javaboy:" + i);
u.setAddress("深圳:" + i);
users.add(u);
}
model.addAttribute("users", users);
return "index";
}
}
public class User {
private Long id;
private String name;
private String address;
// 省略 getter/setter
}
在 IndexController
中返回逻辑视图名 + 数据,逻辑视图名为 index
,意思咱们须要在 resources/templates
目录下提供一个名为 index.html
的 Thymeleaf
模板文件。
- 创立 Thymeleaf
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<tr>
<td> 编号 </td>
<td> 用户名 </td>
<td> 地址 </td>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.address}"></td>
</tr>
</table>
</body>
</html>
在 Thymeleaf
中,通过 th:each
指令来遍历一个汇合,数据的展现通过 th:text
指令来实现,
留神 index.html
最下面引入 thymeleaf
名称空间(最新版并无强制要求)。
配置实现后,就能够启动我的项目了,拜访 /index 接口,就能看到汇合中的数据了:
2.2 手动渲染
后面咱们说的是返回一个 Thymeleaf 模板,咱们也能够手动渲染 Thymeleaf 模板,这个个别在邮件发送时候有用,例如我在 resources/templates 目录下新建一个邮件模板,如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>hello 欢送 <span th:text="${username}"></span> 退出 XXX 团体,您的入职信息如下:</p>
<table border="1">
<tr>
<td> 职位 </td>
<td th:text="${position}"></td>
</tr>
<tr>
<td> 薪水 </td>
<td th:text="${salary}"></td>
</tr>
</table>
<img src="http://www.javaboy.org/images/sb/javaboy.jpg" alt="">
</body>
</html>
这一个 HTML 模板中,有几个变量,咱们要将这个 HTML 模板渲染成一个 String 字符串,再把这个字符串通过邮件发送进来,那么如何手动渲染呢?
@Autowired
TemplateEngine templateEngine;
@Test
public void test1() throws MessagingException {Context context = new Context();
context.setVariable("username", "javaboy");
context.setVariable("position", "Java 工程师");
context.setVariable("salary", 99999);
String mail = templateEngine.process("mail", context);
// 省略邮件发送
}
- 渲染时,咱们须要首先注入一个 TemplateEngine 对象,这个对象就是在 Thymeleaf 的自动化配置类中配置的(即当咱们引入 Thymeleaf 的依赖之后,这个实例就有了)。
- 而后结构一个 Context 对象用来寄存变量。
- 调用 process 办法进行渲染,该办法的返回值就是渲染后的 HTML 字符串,而后咱们将这个字符串发送进来。
3. Thymeleaf 细节
后面两个案例让小伙伴们大抵上了解了在 Spring Boot 中要如何应用 Thymeleaf,接下来,松哥将具体介绍 Thymeleaf 自身的一些具体用法。
3.1 规范表达式语法
3.1.1 简略表达式
${...}
间接应用 th:xx = "${}"
获取对象属性。这个在后面的案例中曾经演示过了,不再赘述。
*{...}
能够像 ${...}
一样应用,也能够通过 th:object
获取对象,而后应用 th:xx = "*{}"
获取对象属性,这种简写格调极为清新,举荐大家在理论我的项目中应用。
<table border="1" th:object="${user}">
<tr>
<td> 用户名 </td>
<td th:text="*{username}"></td>
</tr>
<tr>
<td> 地址 </td>
<td th:text="*{address}"></td>
</tr>
</table>
#{...}
通常的国际化属性:#{...}
用于获取国际化语言翻译值。
在 resources 目录下新建两个文件:messages.properties 和 messages_zh_CN.properties,内容如下:
messages.properties:
message = javaboy
messages_zh_CN.properties:
message = 江南一点雨
而后在 thymeleaf 中援用 message,零碎会依据浏览器的语言环境显示不同的值:
<div th:text="#{message}"></div>
@{...}
- 援用相对 URL:
<script type="text/javascript" th:src="@{http://localhost:8080/hello.js}"></script>
等价于:
<script type="text/javascript" src="http://localhost:8080/hello.js"></script>
- 上下文相干的 URL:
首先在 application.properties 中配置 Spring Boot 的上下文,以便于测试:
server.servlet.context-path=/myapp
援用门路:
<script type="text/javascript" th:src="@{/hello.js}"></script>
等价于:
<script type="text/javascript" src="/myapp/hello.js"></script>
- 绝对 URL:
这个绝对是指绝对于服务器的 URL,例如如下援用:
<script type="text/javascript" th:src="@{~/hello.js}"></script>
等价于:
<script type="text/javascript" src="/hello.js"></script>
应用程序的上下文 /myapp 将被疏忽。
- 协定绝对 URL:
<script type="text/javascript" th:src="@{//localhost:8080/hello.js}"></script>
等价于:
<script type="text/javascript" src="//localhost:8080/hello.js"></script>
- 带参数的 URL:
<script type="text/javascript" th:src="@{//localhost:8080/hello.js(name='javaboy',age=99)}"></script>
等价于:
<script type="text/javascript" th:src="//localhost:8080/hello.js?name=javaboy&age=99"></script>
~{...}
片段表达式是 Thymeleaf 的特色之一,细粒度能够达到标签级别,这是 JSP 无奈做到的。片段表达式领有三种语法:
~{viewName}
:示意引入残缺页面~{viewName ::selector}
:示意在指定页面寻找片段,其中 selector 可为片段名、jquery 选择器等~{::selector}
:示意在当前页寻找
举个简略例子。
在 resources/templates 目录下新建 my_fragment.html 文件,内容如下:
<div th:fragment="javaboy_link"><a href="http://www.javaboy.org">www.javaboy</a></div>
<div th:fragment="itboyhub_link"><a href="http://www.itboyhub.com">www.itboyhub.com</a></div>
这里有两个 div,通过 th:fragment 来定义片段,两个 div 别离具备不同的名字。
而后在另外一个页面中援用该片段:
<table border="1" th:object="${user}" th:fragment="aaa">
<tr>
<td> 用户名 </td>
<td th:text="*{username}"></td>
</tr>
<tr>
<td> 地址 </td>
<td th:text="*{address}"></td>
</tr>
</table>
<hr>
<div th:replace="my_fragment.html"></div>
<hr>
<div th:replace="~{my_fragment.html::javaboy_link}"></div>
<hr>
<div th:replace="~{::aaa}"></div>
通过 th:replace 来援用片段。第一个示意援用残缺的 my_fragment.html
页面;第二个示意援用 my_fragment.html
中的名为 javaboy_link
的片段;第三个示意援用以后页面名为 aaa 的片段,也就是下面那个 table。
3.1.2 字面量
这些是一些能够间接写在表达式中的字符,次要有如下几种:
- 文本字面量:‘one text’, ‘Another one!’,…
- 数字字面量:0, 34, 3.0, 12.3,…
- 布尔字面量:true, false
- Null 字面量:null
- 字面量标记:one, sometext, main,…
案例:
<div th:text="'这是 文本字面量 (有空格)'"></div>
<div th:text="javaboy"></div>
<div th:text="99"></div>
<div th:text="true"></div>
如果文本是英文,并且不蕴含空格、逗号等字符,能够不必加单引号。
3.1.3 文本运算
文本能够应用 +
进行拼接。
<div th:text="'hello'+'javaboy'"></div>
<div th:text="'hello'+${user.username}"></div>
如果字符串中蕴含变量,也能够应用另一种简略的形式,叫做字面量置换,用 |
代替 '...' + '...'
,如下:
<div th:text="|hello ${user.username}|"></div>
<div th:text="'hello'+${user.username}+''+|Go ${user.address}|"></div>
3.1.4 算术运算
算术运算有:+
, -
, *
, /
和 %
。
<div th:with="age=(99*99/99+99-1)">
<div th:text="${age}"></div>
</div>
th:with 定义了一个局部变量 age,在其所在的 div 中能够应用该局部变量。
3.1.5 布尔运算
- 二元运算符:and, or
- 布尔非(一元运算符):!, not
案例:
<div th:with="age=(99*99/99+99-1)">
<div th:text="9 eq 9 or 8 ne 8"></div>
<div th:text="!(9 eq 9 or 8 ne 8)"></div>
<div th:text="not(9 eq 9 or 8 ne 8)"></div>
</div>
3.1.6 比拟和相等
表达式里的值能够应用 >
, <
, >=
和 <=
符号比拟。==
和 !=
运算符用于查看相等(或者不相等)。留神 XML
规定 <
和 >
标签不能用于属性值,所以该当把它们本义为 <
和 >
。
如果不想本义,也能够应用别名:gt (>);lt (<);ge (>=);le (<=);not (!)。还有 eq (==), neq/ne (!=)。
举例:
<div th:with="age=(99*99/99+99-1)">
<div th:text="${age} eq 197"></div>
<div th:text="${age} ne 197"></div>
<div th:text="${age} ge 197"></div>
<div th:text="${age} gt 197"></div>
<div th:text="${age} le 197"></div>
<div th:text="${age} lt 197"></div>
</div>
3.1.7 条件运算符
相似于咱们 Java 中的三目运算符。
<div th:with="age=(99*99/99+99-1)">
<div th:text="(${age} ne 197)?'yes':'no'"></div>
</div>
其中,: 前面的局部能够省略,如果省略了,又同时计算结果为 false 时,将返回 null。
3.1.8 内置对象
根本内置对象:
-
ctx:上下文对象。
-
vars: 上下文变量。
-
locale:上下文区域设置。
-
request:(仅在 Web 上下文中)HttpServletRequest 对象。
-
response:(仅在 Web 上下文中)HttpServletResponse 对象。
-
session:(仅在 Web 上下文中)HttpSession 对象。
-
servletContext:(仅在 Web 上下文中)ServletContext 对象。
在页面能够拜访到下面这些内置对象,举个简略例子:
<div th:text='${#session.getAttribute("name")}'></div>
实用内置对象:
-
execInfo:无关正在解决的模板的信息。
-
messages:在变量表达式中获取内部化音讯的办法,与应用#{…} 语法取得的形式雷同。
-
uris:本义 URL / URI 局部的办法
-
conversions:执行配置的转换服务(如果有)的办法。
-
dates:java.util.Date 对象的办法:格式化,组件提取等
-
calendars:相似于 #dates 然而 java.util.Calendar 对象。
-
numbers:用于格式化数字对象的办法。
-
strings:String 对象的办法:contains,startsWith,prepending / appending 等
-
objects:个别对象的办法。
-
bools:布尔评估的办法。
-
arrays:数组办法。
-
lists:列表的办法。
-
sets:汇合的办法。
-
maps:地图办法。
-
aggregates:在数组或汇合上创立聚合的办法。
-
ids:解决可能反复的 id 属性的办法(例如,作为迭代的后果)。
这是一些内置对象以及工具办法,应用形式也都比拟容易,如果应用的是 IntelliJ IDEA,都会主动提醒对象中的办法,很不便。
举例:
<div th:text="${#execInfo.getProcessedTemplateName()}"></div>
<div th:text="${#arrays.length(#request.getAttribute('names'))}"></div>
3.2 设置属性值
这个是给 HTML 元素设置属性值。能够一次设置多个,多个之间用 ,
分隔开。
例如:
<img th:attr="src=@{/1.png},title=${user.username},alt=${user.username}">
会被渲染成:
<img src="/myapp/1.png" title="javaboy" alt="javaboy">
当然这种设置办法不太好看,可读性也不好。Thymeleaf 还反对在每一个原生的 HTML 属性前加上 th: 前缀的形式来应用动静值,像上面这样:
<img th:src="@{/1.png}" th:alt="${user.username}" th:title="${user.username}">
这种写法看起来更清晰一些,渲染成果和后面统一。
下面案例中的 alt 和 title 则是两个非凡的属性,能够一次性设置,像上面这样:
<img th:src="@{/1.png}" th:alt-title="${user.username}">
这个等价于前文的设置。
3.3 遍历
数组 / 汇合 /Map/Enumeration/Iterator 等的遍历也算是一个十分常见的需要,Thymeleaf 中通过 th:each 来实现遍历,像上面这样:
<table border="1">
<tr th:each="u : ${users}">
<td th:text="${u.username}"></td>
<td th:text="${u.address}"></td>
</tr>
</table>
users 是要遍历的汇合 / 数组,u 则是汇合中的单个元素。
遍历的时候,咱们可能须要获取遍历的状态,Thymeleaf 也对此提供了反对:
- index:以后的遍历索引,从 0 开始。
- count:以后的遍历索引,从 1 开始。
- size:被遍历变量里的元素数量。
- current:每次遍历的遍历变量。
- even/odd:以后的遍历是偶数次还是奇数次。
- first:以后是否为首次遍历。
- last:以后是否为最初一次遍历。
u 前面的 state 示意遍历状态,通过遍历状态能够援用下面的属性。
<table border="1">
<tr th:each="u,state : ${users}">
<td th:text="${u.username}"></td>
<td th:text="${u.address}"></td>
<td th:text="${state.index}"></td>
<td th:text="${state.count}"></td>
<td th:text="${state.size}"></td>
<td th:text="${state.current}"></td>
<td th:text="${state.even}"></td>
<td th:text="${state.odd}"></td>
<td th:text="${state.first}"></td>
<td th:text="${state.last}"></td>
</tr>
</table>
3.4 分支语句
只显示奇数次的遍历,能够应用 th:if,如下:
<table border="1">
<tr th:each="u,state : ${users}" th:if="${state.odd}">
<td th:text="${u.username}"></td>
<td th:text="${u.address}"></td>
<td th:text="${state.index}"></td>
<td th:text="${state.count}"></td>
<td th:text="${state.size}"></td>
<td th:text="${state.current}"></td>
<td th:text="${state.even}"></td>
<td th:text="${state.odd}"></td>
<td th:text="${state.first}"></td>
<td th:text="${state.last}"></td>
</tr>
</table>
th:if 不仅仅只承受布尔值,也承受其余类型的值,例如如下值都会断定为 true:
- 如果值是布尔值,并且为 true。
- 如果值是数字,并且不为 0。
- 如果值是字符,并且不为 0。
- 如果值是字符串,并且不为“false”,“off”或者“no”。
- 如果值不是布尔值,数字,字符或者字符串。
然而如果值为 null,th:if 会求值为 false。
th:unless 的断定条件则与 th:if 齐全相同。
<table border="1">
<tr th:each="u,state : ${users}" th:unless="${state.odd}">
<td th:text="${u.username}"></td>
<td th:text="${u.address}"></td>
<td th:text="${state.index}"></td>
<td th:text="${state.count}"></td>
<td th:text="${state.size}"></td>
<td th:text="${state.current}"></td>
<td th:text="${state.even}"></td>
<td th:text="${state.odd}"></td>
<td th:text="${state.first}"></td>
<td th:text="${state.last}"></td>
</tr>
</table>
这个显示成果则与下面的齐全相同。
当可能性比拟多的时候,也能够应用 switch:
<table border="1">
<tr th:each="u,state : ${users}">
<td th:text="${u.username}"></td>
<td th:text="${u.address}"></td>
<td th:text="${state.index}"></td>
<td th:text="${state.count}"></td>
<td th:text="${state.size}"></td>
<td th:text="${state.current}"></td>
<td th:text="${state.even}"></td>
<td th:text="${state.odd}"></td>
<td th:text="${state.first}"></td>
<td th:text="${state.last}"></td>
<td th:switch="${state.odd}">
<span th:case="true">odd</span>
<span th:case="*">even</span>
</td>
</tr>
</table>
th:case="*"
则示意默认选项。
3.5 本地变量
这个咱们后面曾经波及到了,应用 th:with 能够定义一个本地变量。
3.6 内联
咱们能够应用属性将数据放入页面模版中,然而很多时候,内联的形式看起来更加直观一些,像上面这样:
<div>hello [[${user.username}]]</div>
用内联的形式去做拼接也显得更加天然。
[[...]]
对应于 th:text(后果会是本义的 HTML),[(...)]
对应于 th:utext,它不会执行任何的 HTML 本义。
像上面这样:
<div th:with="str='hello <strong>javaboy</strong>'">
<div>[[${str}]]</div>
<div>[(${str})]</div>
</div>
最终的显示成果如下:
不过内联形式有一个问题。咱们应用 Thymeleaf 的一大劣势在于不必动静渲染就能够间接在浏览器中看到显示成果,当咱们应用属性配置的时候的确是这样,然而如果咱们应用内联的形式,各种表达式就会间接展现在动态网页中。
也能够在 js 或者 css 中应用内联,以 js 为例,应用形式如下:
<script th:inline="javascript">
var username=[[${user.username}]]
console.log(username)
</script>
js 中须要通过 th:inline="javascript"
开启内联。
4. 小结
好啦,Thymeleaf 跟大家也介绍的差不多了,应酬日常的工作应该是能够了。对 Thymeleaf 感兴趣的小伙伴,也能够看看它的官网文档:https://www.thymeleaf.org。
最初,松哥还收集了 50+ 个我的项目需要文档,想做个我的项目练练手的小伙伴无妨看看哦~
需要文档地址:https://gitee.com/lenve/javadoc