# Thymeleaf 模板引擎
springboot 也推荐使用 Thymeleaf(百里香叶),它是一个适用于 Web 和独立环境的现代化服务器端 Java 模板引擎。
使用它的好处在于,可以直接在前端页面填写占位符,这些占位符的实际值由后端进行提供。而不是前后端写一起。
先重新创建一个项目,之后再来感受一下:
- 选择项目
- 选择 Thymeleaf
首先编写一个前端页面,名称为 test.html
,注意,是放在 resource 目录下,在 html 标签内部添加 xmlns:th="http://www.thymeleaf.org"
引入 Thymeleaf 定义的标签属性:
<!DOCTYPE html> | |
<html lang="en" xmlns:th="http://www.thymeleaf.org"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Title</title> | |
</head> | |
<body> | |
<div th:text="${title}"></div> | |
</body> | |
</html> |
再写一个 servlet 类:
@WebServlet("/index") | |
public class TestServlet extends HttpServlet { | |
TemplateEngine engine; | |
@Override | |
public void init() throws ServletException { | |
engine = new TemplateEngine(); | |
// 设定模板解析器决定了从哪里获取模板文件,这里直接使 | |
// 用 ClassLoaderTemplateResolver 表示加载内部资源文件 | |
ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver(); | |
engine.setTemplateResolver(r); | |
} | |
@Override | |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { | |
// 创建上下文,上下文中包含了所有需要替换到模板中的内容 | |
Context context = new Context(); | |
context.setVariable("title", "我是标题"); | |
// 通过此方法就可以直接解析模板并返回响应 | |
engine.process("test.html", context, resp.getWriter()); | |
} | |
} |
启动项目,然后直接访问 index 即可,就能够获得 test.html
。
我这边运行的时候
title
一开始是三个问号,是字符集有问题,我先是在File-setting-Editor-File Encodings
,把里面都改成 UTF-8 即可(html 文件应该也是 UTF-8)。但是还是不行,网上搜到的都是跟 spring 有关的配置,最后想着可能是 HttpResponse 在写入的时候有问题,就加了resp.setCharacterEncoding("utf-8");
,就好了。
虽然 Thymeleaf 在一定程度上分离了前后端,但是其依然是在后台渲染 HTML 页面并发送给前端,并不是真正意义上的前后端分离。
# 语法基础
后端,我们使用 TemplateEngine
对象来将模板文件渲染为最终的 HTML 页面,在处理请求的方法中创建 Context 上下文对象,前端页面中通过上下文提供的内容,来将 Java 代码中的数据解析到前端页面。
前端,使用 th:text
作为当前标签指定内部文本,任何内容都会变成普通文本:
<button th:text="${btn}">123</button> |
后端改一下数据:
context.setVariable("btn", "按钮"); |
加载的时候,按键上的文字就是 “按钮”。
其实请求过来的是一个字符串引用:
甚至支持算术运算,Java 的运算都可以支持,三目运算符,字符串拼接:
<div th:text="${value % 2}"></div> | |
<div th:text="${value % 2 == 0 ? 'yyds' : 'lbwnb'}"></div> | |
<div th:text="${name}+' 我是文本 '+${value}"></div> |
当然也有其他标签,比如
th:utext
标签:传入 HTML 文本,可以添加 html 元素,甚至可以加多个
<!--context.setVariable ("title", "<h1> 我是标题 & lt;/h1><button > 新的按钮 & lt;/button>");--> | |
<div th:utext="${title}"></div> |
th:src th:alt
标签・:th 其实可以对标签的属性进行拼接 ——th:属性名称
,那么属性的值就可以后端提供了。
@Override | |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
Context context = new Context(); | |
context.setVariable("url", "图片链接xxx"); | |
context.setVariable("alt", "图片就是加载不出来啊"); | |
engine.process("test.html", context, resp.getWriter()); | |
} | |
/** | |
<img width="700" th:src="${url}" th:alt="${alt}"> | |
*/ |
为了方便,将模板引擎写成工具类:
public class ThymeleafUtil { | |
private static final TemplateEngine engine; | |
static { | |
engine = new TemplateEngine(); | |
ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver(); | |
engine.setTemplateResolver(r); | |
} | |
public static TemplateEngine getEngine() { | |
return engine; | |
} | |
} |
# 流程控制
其实也是使用一些标签来实现流程控制:
th:if
标签:会根据其中传入的值或是条件表达式的结果进行判断,只有满足的情况下,才会显示此标签。如果标签不是空(为空就是false
)- 如果值是布尔值并且为
true
。
- 如果值是布尔值并且为
- 如果值是一个数字,并且是非零
- 如果值是一个字符,并且是非零
- 如果值是一个字符串,而不是 “错误”、“关闭” 或 “否”
- 如果值不是布尔值、数字、字符或字符串。
// <div th:if="${eval}"> 我是判断条件标签 & lt;/div> | |
@Override | |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
Context context = new Context(); | |
context.setVariable("eval", true); | |
engine.process("test.html", context, resp.getWriter()); | |
} |
th:unless
标签效果完全相反。
th:switch
标签:多分支判断
<div th:switch="${eval}"> | |
<div th:case="1">我是1</div> | |
<div th:case="2">我是2</div> | |
<div th:case="3">我是3</div> | |
<div th:case="*">我是Default</div> | |
</div> |
th:each
标签:遍历
后端代码改成:
context.setVariable("list", Arrays.asList("伞兵一号", "卡布奇诺", "玩游戏要啸着玩", "十七张牌")); | |
/** | |
<ul> 无序列表 | |
<li th:each="title : ${list}" th:text="${title}"></li> | |
</ul> | |
*/ |
前端代码中, th:each
中需要填写 "单个元素名称 : ${列表}",这样,所有的列表项都可以使用遍历的单个元素,只要使用了 th:each
,都会被循环添加。因此最后生成的结果为:
<ul> | |
<li>《伞兵一号的</li> | |
<li>卡布奇诺</li> | |
<li>玩游戏要啸着玩</li> | |
<li>十七张牌</li> | |
</ul> |
还可以获取当前循环的迭代状态,只需要在最后添加 iterStat
即可,从中可以获取很多信息,比如当前的顺序:
<ul> | |
<li th:each="title, iterStat : ${list}" th:text="${iterStat.index}+${title}"/> | |
</ul> |
状态变量在 th:each
属性中定义,并包含以下数据:
当前迭代索引,以 0 开头。这是
index
属性。当前迭代索引,以 1 开头。这是
count
属性。迭代变量中的元素总量。这是
size
属性。每个迭代的迭代变量。这是
current
属性。当前迭代是偶数还是奇数。这些是
even/odd
布尔属性。当前迭代是否是第一个迭代。这是
first
布尔属性。当前迭代是否是最后一个迭代。这是
last
布尔属性。
# 模板布局
有些网页,除了有些部分内容会随着页面跳转改变而改变,有些部分一直都是一个状态,比如 B 站,我们在切换视频时,变化的只有部分区域的内容,有些地方怎么都不会改变
我在 B 站关注了青空の霞光,青空哥真大佬,还不去关注三连。
Thymeleaf 也可以实现,我们可以将不变的地方设定为模板布局,无需每个界面都去编写同样的内容。
我们将每个 html 页面重复公共的地方抽取成一个 template.html
文件:
<!DOCTYPE html> | |
<html xmlns:th="http://www.thymeleaf.org" lang="en"> | |
<body> | |
<div class="head" th:fragment="head-title"> | |
<div> | |
<h1>我是标题内容,每个页面都有</h1> | |
</div> | |
</div> | |
</body> | |
</html> |
那么其他 html 如何使用:
<div th:include="template.html::head-title"></div> | |
<div class="body"> | |
<ul> | |
<li th:each="val, iterStat : ${list}" th:text="${iterStat.index}+${val}"/> | |
</ul> | |
</div> |
中文如果出现乱码:
ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver(); | |
r.setCharacterEncoding("utf-8"); |
如果不使用模板,那我们 html 最开始的写法应该就是:
<div class="head"> | |
<div> | |
<h1>我是标题内容,每个页面都有</h1> | |
</div> | |
</div> | |
<div class="body"> | |
<ul> | |
<li th:each="val, iterStat : ${list}" th:text="${iterStat.index}+${val}"/> | |
</ul> | |
</div> |
th:insert
最简单:它只会插入指定的片段作为标签的主体。相当于在包裹th:insert
的 div 标签里面插入指定片段。th:replace
实际上将标签直接替换为指定的片段,包裹th:insert
的 div 标签会被替换。th:include
和th:insert
相似,但它没有插入片段,而是只插入此片段的内容,以tempalte.html
为例在前端页面展示的时候,最外层的 div 就没有 class 属性了。
同样的,模板也支持参数传递:
<div class="head" th:fragment="head-title(sub)"> | |
<div> | |
<h1>我是标题内容,每个页面都有</h1> | |
<h2 th:text="${sub}"></h2> | |
</div> | |
</div> |
然后在替换位置加入参数即可:
<div th:include="head.html::head-title('这个是第1个页面的二级标题')"></div> |
这个参数也可以由后端传入,前端不需要写死:
<div th:include="template.html::head-title(${text})"></div> |
后端代码:
context.setVariable("text", "abc"); |
# 参考与转载
https://www.yuque.com/qingkongxiaguang/javaweb/hkgbvo#d47f8a5a