SpringBoot2 | 第四篇:整合Thymeleaf模板引擎

SpringBoot 支持如JSPThymeleafFreeMarkerMustacheVelocity等各种模板引擎,同时还为开发者提供了自定义模板扩展的支持。SpringBoot 推荐的 Thymeleaf;语法更简单,功能更强大;

SpringBoot使用上述模板时,默认从 src/main/resources/templates下加载。

[TOC]

基本语法

表达式th 标签配合一起使用

1、表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Simple expressions:(表达式语法)
1.Variable Expressions: ${...}:获取变量值;OGNL;
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
# 例子:
${session.foo}
3)、内置的一些工具对象:
#execInfo : information about the template being processed.
#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).

2.Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
# 补充:配合 th:object="${}" 其中${}指定一个对象,然后*{}直接取属性值。完成一波骚操作
# 例子:
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

3.Message Expressions: #{...}:获取国际化内容
4.Link URL Expressions: @{...}:定义URL;
# 例子:括号内为参数
@{/order/process(execId=${execId},execType='FAST')}
5.Fragment Expressions: ~{...}:片段引用表达式
# 例子:
<div th:insert="~{commons :: main}">...</div>

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)
Special tokens:
No-Operation: _

2、th:属性

  1. th:普通属性:替换 原生 属性的值
  2. th:操作属性:操作 HTML 标签

属性优先级

2018-02-04_123955

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.0.5.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9
  • thymeleaf:3.0.9

1、搭建

360截图179801035199104

2、Hello World

1
2
3
4
5
6
7
8
9
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

// 只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染
public static final String DEFAULT_PREFIX = "classpath:/templates/";

public static final String DEFAULT_SUFFIX = ".html";

命名空间

首先:导入 thymeleaf 的命名空间

1
<html lang="en" xmlns:th="http://www.thymeleaf.org">

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.fatal.fatal;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
* @author: Fatal
* @date: 2018/9/26 0026 17:00
*/
@Controller
public class FatalController {

@GetMapping("/hello")
public String success(Model model) {
model.addAttribute("hello", "Hello Wolrd! thymeleaf渲染成功!");
return "hello";
}

}

hello.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h3 th:text="${hello}">欢迎使用`thymeleaf`模板引擎</h3>
</body>
</html>

显示

360截图17001018727491

3、表达式详解

3.1、Variable Expressions(${})

本质:OGNL 表达式;案例:上面的 Hello World 就是

3.2、Inlining Expressions([[]]、[()])

${} 为例子

Controller

1
2
3
4
5
6
7
8
/**
* 测试行内表达式
*/
@GetMapping("/inlining")
public String inlining(Model model) {
model.addAttribute("hello", "<h2>hello</h2>");
return "inlining";
}

inlining.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Inlining</title>
</head>
<body>
<!-- 转义 -->
<div>[[${hello}]]</div>

<!-- 不转义 -->
<div>[(${hello})]</div>
</body>
</html>

显示

1538206342273

3.3、Selection Variable Expressions(*{})

Controller

1
2
3
4
5
6
7
8
/**
* 测试 *{}
*/
@GetMapping("/test1")
public String test1(Model model) {
model.addAttribute("user", new User().setName("米彩").setAge(18));
return "test1";
}

test1.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试 *{} 表达式</title>
</head>
<body>
<span th:object="${user}">
<span th:text="*{name}">这里是静态的名字</span>
<span th:text="*{age}">这里是静态的年龄</span>
</span>
</body>
</html>

显示

1538206558530

3.4、Message Expressions(#{})

步骤

No.1

准备静态文件,我这里从 bootstrap 官网下载了4.0 版本的登录页面的模板

1538203469124

No.2

编写国际化配置文件,抽取页面需要显示的国际化内容

  • 要求

    需编写 ==一个 普通== 配置文件和 ==多个 国际化== 配置文件。命名格式如下:

    • 普通配置文件:基础名
    • 国际化配置文件:基础名 + 下划线 + 国际化标识zh_CNen_US
    • 文件类型为 properties
    • 如:message.proerties、message_zh_CN.proerties、message_en_US.proerties
    • 如果命名规范,创建第一个国际化配置文件的时候,SpringBoot 会自动识别出来并帮你创建 Resource Bundle “基础名” 文件夹;

    • 如果配置文件书写正确,在 html 页面上使用 th结合#{} 的语法会有 联想

      1538206083223

  1. resource 下创建文件夹 i18n(名字你随意)

  2. i18n 下创建 基础名.properties

  3. i18n 下创建 基础名_zh_CN.properties(这个时候如果你命名规则正确,SpringBoot 会自动识别,并生成Resource Bundle “基础名” 文件夹)

  4. 右键单击 Resource Bundle “基础名”,选择 New,选择 Add Property File Resource Bundle 创建另一个国际化配置文件

  5. 点击三个配置文件中的其中一个。切换到 Resource Bundle 视图

    1538205956420

  6. 右键点击 基础名(或点击 ‘+‘),选择 New Property 添加属性,然后在右方 填写对应的属性值 即可

    1538205991235

No.3

在全局配置文件中指定 国际化基础名

格式:class路径下.基础名

1
2
3
4
spring:
# 配置国际化基础名
messages:
basename: i18n.login
No.4

在 test2.html 页面上用 #{} 取值,并在国际化链接上加上国际化标识参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="/favicon.ico" th:href="@{/favicon.ico}">

<title>测试 #{} 表达式(国际化)</title>

<!-- Bootstrap core CSS -->
<link href="/dist/css/bootstrap.min.css" th:href="@{/dist/css/bootstrap.min.css}" rel="stylesheet">

<!-- Custom styles for this template -->
<link href="/signin.css" th:href="@{/signin.css}" rel="stylesheet">
</head>

<body class="text-center">
<form class="form-signin">
<img class="mb-4" src="/assets/brand/bootstrap-solid.svg"
th:src="@{/assets/brand/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control" th:placeholder="#{login.email}"
placeholder="Email address" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control" th:placeholder="#{login.password}"
placeholder="Password" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit"
th:text="#{login.btn}">Sign in
</button>
<!-- 国际化链接上加上国际化标识参数 -->
<a class="btn btn-sm" th:href="@{/test2(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/test2(l='en_US')}">English</a>
<p class="mt-5 mb-3 text-muted">&copy; 2017-2018</p>
</form>
</body>
</html>
No.5

自定义区域解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.fatal.compoment;

import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
* 自定义区域解析器
* @author: Fatal
* @date: 2018/9/28 0028 21:50
*/
public class MyLocaleResolver implements LocaleResolver {

@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("l");
// 如果没带则获得系统默认的
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(l)) {
String[] split = l.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}

@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

}

}
No.6

配置自定义区域解析器到 WebMvcConfigurer 组件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.fatal.config;

import com.fatal.compoment.MyLocaleResolver;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @author: Fatal
* @date: 2018/9/28 0028 21:54
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
No.7

视图映射(两种方式)

  • 处理器映射器 @RequestMappping

    1
    2
    3
    4
    @GetMapping("/test2")
    public String test2(Model model) {
    return "test2";
    }
  • 在 WebMvc 中添加 视图控制器

    WebMvcConfigurer 中重写 addViewControllers 方法

    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 添加视图控制器
    */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    // `映射路径`会去模板中找对应的`视图`
    registry.addViewController("/test2").setViewName("test2");
    }
显示

首次访问

360截图16720405163217

点击 English

1538203330510

点击 中文

1538203377885

Controller

1
2
3
4
5
6
7
/**
* 测试 @{}
*/
@GetMapping("/test3")
public String test3(Model model) {
return "test3";
}

test3.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试 @{} 表达式</title>
<!-- 使用 @{} 表达式引入 css文件 -->
<link rel="stylesheet" th:href="@{http://localhost:8080/css/test3.css}">
<!-- @{} 我们只需写classpath下的路径即可 -->
<script type="text/javascript" th:src="@{/css/test3.js}"></script>
</head>
<body>
<div>字体为红色,代表成功</div>
</body>
</html>

显示

1538206850582

3.6、Fragment Expressions(~{})

姿势解析

模板文件
1
2
3
4
5
6
7
8
9
<!-- 方式一:使用`th:fragment`标签命名片段 -->
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>

<!-- 方式二:只需在模板文件的片段上加个`id属性` -->
<div id="copy-section">
&copy; 2011 The Good Thymes Virtual Grocery
</div>
三种引入公共片段的th属性
  • th:insert:将公共片段整个插入到声明引入的元素中

  • th:replace:将声明引入的元素替换为公共片段

  • th:include:将被引入的片段的内容包含进这个标签中

如何取?
  • 标签:th:insertth:replaceth:include
  • 表达式: ~{templatename::selector}, ~{templatename::fragmentname}
1
2
3
4
5
<!-- 上方标签和表达式各取一种-->
<div th:insert="~{footer :: #copy-section}"></div>
<div th:insert="footer :: #copy-section"></div>
<div th:insert="~{footer :: copy}"></div>
<div th:insert="footer :: copy"></div>
  • 注:
  1. ~{} 与这三个标签组合时,~{} 可以省略
  2. templatename:classpath下的模板文件的相对路径名(不需要后缀

Demo1

测试选择器和片段名

Controller
1
2
3
4
5
6
7
/**
* 测试 ~{} (选择器和片段名)
*/
@GetMapping("/test4")
public String test4(Model model) {
return "test4";
}
bar.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- bar代码抽取 -->
<div id="side">
这是侧边
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- footer代码抽取 -->
<footer th:fragment="copy">
&copy; 2018/9/29 The Good Thymes Virtual Grocery
</footer>
</body>
</html>
test4.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试 ~{} 表达式</title>
</head>
<body>
<h1>~{}</h1>

<h3>~{templatename::fragmentname}:</h3>
<!-- 格式: classpath下模板文件的相对路径(具体到模板名,不需要后缀) :: 片段名-->
<div th:insert="~{commons/footer :: copy}"></div>
<hr>
<h3>~{templatename::selector}:</h3>
<!-- 格式: classpath下相对路径(具体到模板名,不需要后缀) :: 选择器-->
<div th:insert="~{commons/bar :: #side}"></div>
<hr>
<h3>th:insert一起用,可以省略~{}:</h3>
<!-- 与th:insert一起用,可以省略~{} -->
<div th:insert="commons/footer.html :: copy"></div> <!-- 这里有红线,但不影响正常运行 -->
<!--<div th:insert="commons/footer :: copy"></div>-->
<div th:insert="commons/bar.html :: #side"></div>
<!--<div th:insert="commons/bar :: #side"></div>-->

</body>
</html>
显示

1538210941733

Demo2

测试 th:insert,th:replace, th:include

Controller
1
2
3
4
5
6
7
/**
* 测试 ~{} (th:insert,th:replace, th:include)
*/
@GetMapping("/test5")
public String test5(Model model) {
return "test5";
}
test5.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试 ~{} 表达式</title>
</head>
<body>
<h1>~{}</h1>

<!-- th:insert -->
<hr>
<div th:insert="commons/footer.html :: copy"></div>

<!-- th:replace -->
<hr>
<div th:replace="commons/footer.html :: copy"></div>

<!-- th:include -->
<hr>
<div th:include="commons/footer.html :: copy"></div>

</body>
</html>
显示

1538212323861

4、th:属性

th:each

语法: th:each=”元素名: ${集合名}”

Demo

Controller
1
2
3
4
5
6
7
8
9
10
11
/**
* 测试 th:each
*/
@GetMapping("/test6")
public String test6(Model model) {
List<String> strings = Arrays.asList("小米", "米彩", "米琪", "米澜");
Map<Integer ,String> collect = strings.stream()
.collect(Collectors.toMap(e -> random.nextInt(100), Function.identity()));
model.addAttribute("users",collect);
return "test6";
}
test6.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试 th:each</title>
</head>
<body>
<h1>th:each</h1>

<div th:each="user:${users}">
<div th:text="${user.key}"></div>
<div th:text="${user.value}"></div>
<hr>
</div>

</body>
</html>

th:if

Demo

Controller
1
2
3
4
5
6
7
8
/**
* 测试 th:if
*/
@GetMapping("/test7")
public String test7(Model model) {
model.addAttribute("age",18);
return "test7";
}
test7.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试 th:if</title>
</head>
<body>
<h1>th:if</h1>
<!-- 使用简写 -->
<div th:if="${age eq 18}">等于18(使用简写)</div>
<!-- 使用符号 -->
<div th:if="${age == 18}">等于18(使用符号)</div>
</body>
</html>

Thymeleaf 热加载

1
2
3
spring:
thymeleaf:
cache: false

操作:Ctrl + F9 即可

总结

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter4