# 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:includeth: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