传递不同的 fragment 给 thymeleaf 模版引擎

thymeleaf 是一个很好用的模版引擎,其中 fragment 关键字可以让我们重用一些 html 元素,我们知道可以通过设定 Model 的 Attribute 来向 thymeleaf 传递 fragment 参数,那么能不能通过这种方式来使用不同的 fragment 呢?

1 环境

本文假定已经有了一个配置好的包含 thymeleaf 的 springboot 项目。首先,创建一个 TestController:

@Controller
public class TestController {
    @RequestMapping(path = "test")
    public ModelAndView test(ModelAndView mav) {
	mav.setViewName("test");
	return mav;
    }
}

所以我们的 resources/templates 下应该有一个 test.html:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="bootstrap-4.3.1-dist/css/bootstrap.min.css">
</head>
<body>
  test.html body
</body>

</html>

接下来我们在这个基础上展示如何使用 fragment

2 使用 fragment

我们可以新建一个 fragments.html 来存放 fragment:

<body>
  <div th:fragment="fragment1()">
    this is fragment 1
  </div>
  <div th:fragment="fragment2()">
    this is fragment 2
  </div>
</body>

然后,我们修改 test.html 的 body 来使用 fragment:

<body>
  test.html body <br>
  <div th:replace="fragments :: fragment1()"></div>
</body>

运行这个程序后,在浏览器访问 localhost:8080/test,应该可以看到以下结果

test.html body
this is fragment 1

接下来我们试试把 fragment1 替换成参数

3 fragment 参数

首先修改一下 TestController, 增加参数 frag

@Controller
public class TestController {
    @RequestMapping(path = "test")
    public ModelAndView test(ModelAndView mav) {
	mav.addObject("frag", "fragments :: fragment1()");
	mav.setViewName("test");
	return mav;
    }
}

然后在页面上输出 frag,看有没有正确传递给 thymeleaf

<body>
  test.html body <br>
  <p th:text="${frag}"></p>
  <div th:replace="fragments :: fragment1()"></div>
</body>

修改完成后访问 localhost:8080/test,浏览器应该有以下输出:

test.html body
fragments :: fragment1()

this is fragment 1

所以 thymeleaf 可以正确拿到 frag 的值,那我们能不能让 th:replace 也使用 frag 呢?我们把 test.html 修改成这样:

<body>
  test.html body <br>
  <p th:text="${frag}"></p>
  <div th:replace="${frag}"></div>
</body>

再次访问 localhost:8080/test,结果是报错,你可以在 stacktrace 中找到以下提示:

Caused by: org.attoparser.ParseException: Error resolving template [fragments :: fragment1()], template might not exist or might not be accessible by any of the configured Template Resolvers (template: "test" - line 9, col 8)
	at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
	at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
	at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
	... 48 more
Caused by: org.thymeleaf.exceptions.TemplateInputException: Error resolving template [fragments :: fragment1()], template might not exist or might not be accessible by any of the configured Template Resolvers (template: "test" - line 9, col 8)

模版引擎没有办法解析这个模版,可是如果写明 th:replace="fragments :: fragment1()" ,就不会有问题,这是一个让人很困惑的问题,先说我们如何解决,让我们修改一下 test.html

<body>
  test.html body <br>
  <p th:text="${frag}"></p>
  <div th:replace="__${frag}__"></div>
</body>

再访问 localhost:8080/test:

test.html body
fragments :: fragment1()

this is fragment 1

成功了!我们可以在代码中把 frag 的值修改为 fragment2,再试一下结果,这里就不赘述了

4 结论

「__${frag}__」 的写法在 thymeleaf 中称作 预处理 ,所以我们可以推测, thymeleaf 拿到字符串后会做一次处理,比如把 ${frag} 替换为 fragment1,然后就当成新字符串拿来使用了,但是其实我们是要使用字符串所代表的模版,这个时候就要告诉 thymeleaf,解析成字符串只是第一步,接下来我们还要转换成模版使用。