Thymeleaf - 使用方法及国际化(超详细)
Thymeleaf简介
Thymeleaf是一个和Velocity、FreeMarker 类似的模板引擎,它在有网络和无网络的环境下皆可运行。因为它支持html原型,在html的标签里增加了额外的属性来达到模板+数据的展示方式。浏览器解释html时会忽略未定义的标签属性,所以thymeleaf的模板可以静态地运行。当有数据返回到页面时,Thymeleaf标签会动态地替换掉静态内容,使页面动态显示。
它与SpringBoot完美结合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解析器。它可以快速实现表单绑定、属性编辑器、国际化等功能。
在SpringBoot中的配置
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 1
- 2
- 3
- 4
2、创建模板文件夹
SpringBoot自动为Thymealf注册了一个视图解析器ThymealfViewResolver,并且配置了模板(HTML)的位置,与JSP类似的前缀+视图名+后缀的风格。我们可以进到它的配置文件(ThymeleafProperties)里去看:
可以发现,如果我们没有在yml
中进行配置,则它去找的视图文件默认是在resources
配置文件夹下的templates
文件夹里,后缀为.html
。我们也可以在配置文件中进行配置,即图中所标注的spring.thymeleaf
下。
yml配置文件
Controller层返回页面示例
Thymeleaf在Html页面中的基本
使用
1.要在html页面中使用thymeleaf的标签,必须引入名称空间。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 1
2.表达式的使用
在html页面中,要使用thymealf的标签,只需在原标签名前加th:
即可,如th:src
、th:href
、th:text
等
①${}
,变量表达式,它可以获取到Controller层存入Model的值
// 后端Controller层代码
model.addAttribute("name","柳成荫");
User user = new User("柳成荫",22); // 姓名、年龄
model.addAttribute("user",user);
- 1
- 2
- 3
- 4
// html页面中获取并显示
<span th:text="${name}">九月</span>
<span th:text="${user.age}">18</span>
- 1
- 2
- 3
前面说过,当有数据传递过来时,则动态数据会替换静态数据。
②*{}
,选择变量表达式,可以省略对象名,直接获取属性值
<div th:object="${user}">
<p th:text="*{name}">九月</p>
<p th:text="*P{age}">18</p>
</div>
- 1
- 2
- 3
- 4
③#{}
,Message表达式,它主要是从国际化配置文件中取值,这里暂不做示例,文章后面将会示例从国际化配置文件中取值。
3、URL的使用
①绝对网址,绝对URL用于创建到其他服务器的链接,需要指定协议名称http
或者https
,如:
<a th:href="@{https://www.baidu.com}">百度</a>
- 1
②上下文相关URL,即与项目根相关联的URL,这里假设我们的war包为app.war,且没有在tomcat的server.xml
配置项目的路径(Context),则在Tomcat启动之后,就会在webapps文件下产生一个app文件夹,此时app
就是上下文路径(Context)
<!-- 在页面这样写 -->
<a th:href="@{/blog/search}">跳转</a>
<!-- Thymeleaf解析之后是这样的 -->
<a href="/app/blog/search">跳转</a>
- 1
- 2
- 3
- 4
③服务器相关URL,它与上下文路径很相似,它主要用于一个Tomcat下运行有多个项目的情况。比如说我们当前项目为app
,如果tomcat还运行着一个otherApp
,我们就可以通过该方法访问otherApp
的请求路径。
<!-- 在页面这样写 -->
<a th:href="@{~/otherApp/blog/search}">跳转</a>
<!-- Thymeleaf解析之后是这样的 -->
<a href="/otherApp/blog/search">跳转</a>
- 1
- 2
- 3
- 4
④有时候我们需要页面带参数传递到后端,则可以使用下面这个方法
<!-- 在页面这样写 -->
<a th:href="@{/blog/search(id=3,blogName='Java')}" >跳转</a>
<!-- Thymeleaf解析之后是这样的,这里忽略项目路径 -->
<a href="/blog/search?id=3&blogName=Java" >跳转</a>
<!-- 还可以这样写,实现restful风格的效果 -->
<a th:href="/blog/{id}/search(id=3&blogName=Java)">跳转</a>
<!-- Thymeleaf解析之后是这样的,这里忽略项目路径 -->
<a href="/blog/3/search?blogName=java">跳转</a>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4、字面值
有时候,我们需要在指令中填入基本类型如:字符串、数值、布尔等,不希望Thymeleaf去给我们解析,可以这样做:
①字符串字面值
<!-- 在页面中这样写 -->
<span th:text="‘Thymealf’ + 3">templates</span>
<!-- Thymeleaf解析后 -->
<span>Thymealf3</span>
- 1
- 2
- 3
- 4
②数字字面值
<!-- 在页面中这样写 -->
<span th:text="1 + 3">templates</span>
<!-- Thymeleaf解析后 -->
<span>4</span>
- 1
- 2
- 3
- 4
③布尔字面值:为true和false
5、拼接
传统拼接需要用''
来进行普通字符串和表达式的拼接,Thymeleaf中进行了简化,只需将拼接的内容块使用||
包裹即可:
<span th:text="|欢迎您,${user.name}|">九月</span>
- 1
6、运算符
①因为html里会将<
和>
符号进行解析,所以不能直接使用,但是如果在{}
内使用,是不需要转换的
> gt 即greater than,大于
< lt 即less than,小于
>= ge 即greater equal,大于等于
<= le 即less equal,小于等于
- 1
- 2
- 3
- 4
②三元运算符
<span th:text="${false} ? '男' : '女'">性别</span>
<>
- 1
- 2
③空值判断
<!-- 如果user.name为空,则显示 空值显示 这几个字 -->
<span th:text="${user.name} ?: ‘空值显示’"></span>
- 1
- 2
7、内联写法
①[()]
,解析输出,会解析内容里的html
标签
<span>[(${user.name})]</span>
- 1
②[[]]
,原样输出,不会解析内容里的html
标签
<span>[[${user.name}]]</span>
- 1
8、局部变量
可以将后端传来的数据赋值给一个局部变量
,这个局部变量
只能在标签内部
使用,外部是不可以的
<div th:with="user=${userList[0]}">
<span th:text="${user.name}">昵称</span>
</div>
- 1
- 2
- 3
9、判断
①th:if
,满足条件才显示标签包裹的(含标签)的内容
<span th:if="${true}">显示</span>
<span th:if="${user.age == 18}">18岁显示</span>
- 1
- 2
②th:unless
,与th:if
相反,不满足条件时显示
<span th:unless='${true}'>不显示</span>
- 1
③th:switch
,switch的效果一致
<div th:witch="${user.name}">
<span th:case="柳成荫">柳成荫</span>
<span th:case="九月">九月</span>
<span th:case="寻宝">寻宝</span>
</div>
- 1
- 2
- 3
- 4
- 5
10、迭代
th:each
,迭代一个集合/数组,可以使用它的内置对象stat
获取更多的信息。
先列出内置对象stat
的用法:
index:角标,从0开始
count:元素的个数,从1开始,当前遍历到第几个
size:元素的总个数
current:当前遍历到的元素
even/odd:是否为奇/偶,都是返回true或false的布尔结果
first/last:是否第一/最后,都是返回true或false的布尔结果
- 1
- 2
- 3
- 4
- 5
- 6
使用th:each
<tr th:each="user:${userList}">
<td th:text="${user.name}"></td>
<td th:text="|当前迭代到第${stat.count}个了|"></td>
</tr>
- 1
- 2
- 3
- 4
11、环境相关对象
1、${#ctx}
:上下文对象,可用于获取其他内置对象
2、${#vars}
:上下文变量。
3、${#locale}
:上下文区域设置。
4、${#request}
:HttpServletRequest对象。
5、${#response}
:HttpServletResponse对象。
6、${#session}
:HttpSession对象。
7、${#servletContext}
:ServletContext对象。
具体怎么使用,可以自行百度,这里以session
为例:
<span th:text="${session.user.name}">存储在session的User对象</span>
- 1
12、全局对象功能
1、#strings
:字符串工具类
2、#lists
:List 工具类
3、#arrays
:数组工具类
4、#sets
:Set 工具类
5、#maps
:常用Map方法。
6、#objects
:一般对象类,通常用来判断非空
7、#bools
:常用的布尔方法。
8、#execInfo
:获取页面模板的处理信息。
9、#messages
:在变量表达式中获取外部消息的方法,与使用#{…}语法获取的方法相同。
10、#uris
:转义部分URL / URI的方法。
11、#conversions
:用于执行已配置的转换服务的方法。
12、#dates
:时间操作和时间格式化等。
13、#calendars
:用于更复杂时间的格式化。
14、#numbers
:格式化数字对象的方法。
15、#aggregates
:在数组或集合上创建聚合的方法。
16、#ids
:处理可能重复的id属性的方法。
具体怎么使用,可以自行百度,这里仅以#dates
为例,格式化时间:
<span th:text="${#dates.format(blog.updateTime)},'yyyy-MM-dd HH:mm:ss'"></span>
- 1
13、class属性增加
使用th:classappend
,可以增加class
元素类名。通常用在如导航栏上,当导航栏上某个标签被选中,它会与其他没有被选中的有不同的样式。
<a class="item m-mobile-hide’ th:classappend='${n==1} ? ‘active’">首页</a>
<!-- 如果n==1成立,则解析如下 -->
<a class="item m-mobile-hide active’">首页</a>
- 1
- 2
- 3
Thymeleaf在Html页面中的布局
使用
定义片段
和引入片段
是非常好的功能,在一个项目中的各个页面里,通常他们大部分导航栏和底部是相同的。我们可以创建一个页面专门用来定义重复使用片段,然后在其他页面做引入。
1、定义片段
①使用th:fragment
定义通用片段 - 普通片段
<nav th:fragment="header">
<a href="#">首页</a>
<a href="#">分类</a>
</nav>
- 1
- 2
- 3
- 4
②使用th:fragment
定义通用片段 - 带参片段
如下代码,如果我们当前是在首页,则首页这个标签就会多一个样式,而分类就没有。我们只需要在引入这块片段的代码里传递一个参数过来进行判断即可。
<nav th:fragment="header(n)">
<a class="item’ th:classappend='${n==1} ? ‘active’">首页</a>
<a class="item’ th:classappend='${n==2} ? ‘active’">分类</a>
</nav>
- 1
- 2
- 3
- 4
③使用id
来定义片段 - 不推荐
<nav id="header">
<a href="#">首页</a>
<a href="#">分类</a>
</nav>
- 1
- 2
- 3
- 4
2、引入片段 - 下文中的_fragment
为定义片段的html页面
①将公共的标签插入
到指定标签中 - 该方法不可以省略~{}
<div th:insert="~{_fragment::header}">
<!-- 被引入的片段会插入到div标签内部 -->
</div>
- 1
- 2
- 3
②将公共的标签替换
指定的标签 - 该方法可以省略~{}
推荐
使用该引入方式,这样做,可以保证在没有网络(动态数据)的情况下,其他页面也有内容可以显示。
<!-- 被引入的片段会替换div标签的内容(含div) -->
<div th:replace="~{_fragment::header}">
<a href="#">链接</a>
</div>
- 1
- 2
- 3
- 4
③将公共的标签包含
到指定的标签 - 该方法可以省略~{}
<div th:include="~{_fragment::header}">
<!--
被引入的片段会被包含到div标签内部
但是最外层的标签不会被包含进来,即只包含th:fragment所在标签内部的内容
-->
</div>
- 1
- 2
- 3
- 4
- 5
- 6
④上诉三种方法,可以用来引入使用id
定义片段的 - 不推荐
<nav th:insert="~{_fragment::#header}">
<!-- 不推荐id定义片段和引入这种片段 -->
</nav>
- 1
- 2
- 3
⑤引入带参片段 - 重要
<nav th:replace="header(2)">
<a class="item’ th:classappend='${n==1} ? ‘active’">首页</a>
<a class="item’ th:classappend='${n==2} ? ‘active’">分类</a>
</nav>
- 1
- 2
- 3
- 4
3、th:fragment
更新数据
很多时候,在页面里我们需要使用Ajax
异步请求数据,我们希望只更新某一块的数据,其他地方不变。这时,就需要th:fragment
来做了,文章前面有提到return "index :: blogList"
这种方式,它就是配合th:fragment
来做的。
<!-- 其他代码 -->
<div th:fragment="userInfo">
<span th:text="${user.name}">九月</span>
<span th:text="${user.age}">18</span>
</div>
<!-- 其他代码 -->
- 1
- 2
- 3
- 4
- 5
- 6
public String userInfo(Model model){
model.addAttribute(user,new User("柳成荫",22));
return "index :: userInfo";
}
- 1
- 2
- 3
- 4
th:fragment
和th:replace
是用在合适的地方非常好用,常见的css块、js块、导航栏、底部区域。
4、块级标签th:block
th:block
是thymeleaf提供的块级标签,其特殊性在于Thymeleaf模板引擎在处理<th:block>
的时候会删掉它本身,标签本身不显示,而保留其内容。
①常见用法 - HTML部分
<!-- 控制几个标签是否一起显示/不显示 -->
<th:block th:if="...">
<div id="div1">我和div2一起</div>
<div id="div2">我和div1一起</div>
</th:block>
- 1
- 2
- 3
- 4
- 5
<!-- 循环统计标签 -->
<table>
<th:block th:each="...">
<tr>...</tr>
<tr>...</tr>
</th:block>
</table>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
②常见用法 - JS部分
有时候js
的引入可能也是其他页面公有的,我们想定义一个js片段
,然后在其他页面引入。可以使用<div>
将js片段
包裹起来,然后将这个<div>
定义成一个片段,但这种做法并不好。这时就用到了th:block
了
<!-- JS公用部分 - 定义片段 -->
<th:block th:fragment="script">
<script src="../static/js/jquery-3.3.1.min.js" th:src="@{/js/jquery-3.3.1.min.js}"></script>
<script src="../static/js/semantic.min.js" th:src="@{/js/semantic.min.js}"></script>
</th:block>
- 1
- 2
- 3
- 4
- 5
<th:block th:replace="_fragment::script">
<!-- 这样就解决了JS共用的问题了 -->
<script src="../static/js/jquery-3.3.1.min.js"></script>
</th:block>
- 1
- 2
- 3
- 4
可能有人会问,这个th:block
会不会影响静态页面?实际上,它是不会影响静态页面的。如果你实在是担心,我们可以用可以使用下面这种方法
<!--/*/<th:block th:replace="_fragments :: script">/*/-->
<!-- 这样就解决了JS共用的问题了 -->
<script src="../static/js/jquery-3.3.1.min.js"></script>
<!--/*/<th:block th:replace="_fragments :: script">/*/-->
- 1
- 2
- 3
- 4
注释的使用
可以看到,上面我们使用了<!-- -->
的方式将th:block
注释掉了,但是你也发现,我们在里面使用了/*/ /*/
来包裹了标签。它是thymeleaf注释的一种,它在运行时,就不会把上面那些注释了的代码看做是注释,而是当做正常的处理。
/*
是另外一种,它的作用就是thymeleaf运行时,把包裹起来的内容注释掉。
①/*/
注释
<!-- 下面这行代码再静态页面中确实是被注释了,但是动态运行时,则不会被注释 -->
<!--/*/<th:block th:replace="_fragments :: script">/*/-->
- 1
- 2
②/*
注释
<!--
下面的注释在静态页面的确注释了,但是请注意,它注释的是/*和*/
而动态运行时,被/*和*/包裹的都要被注释掉
这样做的目的是保证静态运行时可以看到静态效果,动态运行时看到动态效果
-->
<div>
<span th:each="blog:${blogList}" th:text="${blog.name}">博客1</span>
<!-- /* -->
<span th:each="blog:${blogList}">博客2</span>
<span th:each="blog:${blogList}">博客3</span>
<span th:each="blog:${blogList}">博客4</span>
<!-- */ -->
</div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
JS模板预处理
模板引擎不仅可以渲染html,也可以对JS中的进行预处理
。而且为了在纯静态环境下可以运行。在script
标签中通过th:inline="javascript"
来声明这是要特殊处理的js脚本。
<script th:inline="javascript">
var username = /*[[${user.name}]]*/ ‘默认’;
var age = /*[[${user.age}]]*/ ‘18’;
console.log(username);
console.log(age);
</script>
- 1
- 2
- 3
- 4
- 5
- 6
国际化
在SpringBoot项目里面做国际化,只需要在resources/i18n
路径下创建xxx.properties
、xxx_en_US.properties
、xxx_zh_CN.properties
即可。具体怎么做,将在下文进行演示。
在SpringBoot中有一个messageSourceAutoConfiguration
,它会自动管理国际化资源文件。
也就是说,我们在resources/i18n
创建的国际化配置文件前缀
名为message
时,SpringBoot会自动加载。
当然,我们也可以自己定义国际化文件名称,这里我们将国际化配置文件命名为login
。
1、去yml
配置文件中配置国际化
spring
messages: i18n.login
- 1
- 2
2、要确保文件编码是UTF-8
,可以到idea的设置里去设置并让其自动转换,Editor
->File Encodings
3、创建i18n
文件夹及3个国际化配置文件,如图
其中login.properties
是基础配置文件(默认),如果你的浏览器它是其他语言比如法语,是没有办法国际化的,所以它就会采用login.properties
在idea里只要创建两个国际化的配置文件就会自动加入到Resource Bundle 'xxx'
,当然我们还是需要创建三个properties
配置文件。后缀必须按格式写,_en_US
就是英文,_zh_CN
就是中文。
4、编写国际化
我们直接点进login.properties,可以看到下方有个切换到Resource Bundle
的按钮,点击切换,添加国际化,然后分别在右侧写上对应国际化语言
想添加多少,就可以添加多少。
5、可以在Thymeleaf
页面进行获取了,用到前面所说的#{}
<button type="submit" th:text="#{login.btn}"></button>
- 1
6、一般来说我们在页面会做个切换中英文的按钮来进行切换
<a th:href="@{/login(lan='zn_CN')}">中文</a>
<a th:href="@{/login(lan='en_US')}">英文</a>
- 1
- 2
7、做完上诉步骤,我们还需要编写一个本地解析器来进行解析
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 接收语言的参数 - 传进来的就是形如'zh_CN'这样的参数
String lan = request.getParameter("lan");
// 使用默认的语言 - 在文中就是login.properties文件里配置的
Locale locale = Locale.getDefault();
// 判断接收的参数是否为空,不为空就设置为该语言
if(!StringUtils.isEmpty(lan)){
// 将参数分隔 - 假设传进来的是'zh_CN'
String[] s = lan.split("_");
// 语言编码:zh 地区编码:CN
locale = new Locale(s[0],s[1]);
}
return locale;
}
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setLocale</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Locale locale<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
8、我们还需要写一个配置文件来表明国际化解析器
是用的我们自己写的
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
国际化就这样完成了,通过上面定义的切换语言按钮就可以切换了。