Thymeleaf模板引擎

x33g5p2x  于2021-12-06 转载在 其他  
字(17.1k)|赞(0)|评价(0)|浏览(479)

前言

在经典的java单体项目中(前后端不分离),页面的编写往往采用JSP技术(Java Server Page),JSP的最大特点是可以在页面中编写java代码,实际上jsp经过转化生成Servlet,再编译生成*.class文件存储在WEB容器中,而对用户请求的处理正是由Servlet完成的。

使用JSP开发的主要问题是页面过于混乱,在HTML中编写JAVA代码往往只能由后台工程师完成,极大的提升了开发的难度,而页面的调试也需要先转换为Servlet再打包才能看到执行结果。SpringBoot项目默认使用内置的tomcat服务器,项目通常会被打成jar包放在jvm上直接执行(减少了部署WEB容器的环节),此时再采用JSP技术,就需要自建WEB-INF/web.xml文件,打成war包使用外部容器部署,这样的反而失去自身开发简洁的优势。

Thymeleaf是SpringBoot推荐使用的模板引擎,使用Thymeleaf可以按照HTML的方式编写代码,也同时可以实现JSP中的相关动态操作,而且不会生成*.class文件。

一、基础使用

想要在项目中使用thymeleaf,首先需要导入spring-boot-starter-thymeleaf依赖包。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  4. <version>2.5.6</version>
  5. </dependency>

创建thymeleaf文件保存路径,使用thymeleaf语法编写的文件应该放在项目资源路径下。在src/mian下新建一个view文件夹并标记为资源根路径。

在src/mian/view下新建templates文件夹(必须叫这个名字),在templates文件夹中新建一个message子文件夹,在子文件夹内新建message_show.html页面。

编写message_show.html页面并引入thymeleaf命名空间。

  1. <!DOCTYPE html>
  2. <!-- 引入Thymeleaf命名空间 -->
  3. <html xmlns:th="http://www.thymeleaf.org">
  4. <head>
  5. <meta charset="UTF-8" />
  6. <title>Thymeleaf模板引擎</title>
  7. </head>
  8. <body>
  9. <p th:text="'message: ' + ${message}" ></p>
  10. </body>
  11. </html>

创建MessageAction处理类,接收message并传递到message_show.html页面显示。

  1. @Controller
  2. @RequestMapping("/message/*")
  3. public class MessageAction {
  4. /** * 接收message并跳转至message_show.html页面 * @param model Model * @param message * @return message_show */
  5. @GetMapping("/page")
  6. public String messagePage(Model model, String message) {
  7. model.addAttribute("message", message);
  8. return "message/message_show";
  9. }
  10. }

启动项目,访问:http://localhost:8080/message/page?message=hello Thymeleaf !!! 发现页面显示成功。

二、环境配置

thymeleaf允许自定义配置,配置项写在application.yml文件中,例如修改保存的文件夹和页面后缀:

  1. server:
  2. port: 8080
  3. spring:
  4. thymeleaf:
  5. prefix: classpath:/pages/
  6. suffix: .page

实际上thymeleaf的所有配置属性保存在ThymeleafProperties类中,通过此类可以查看具体都存在哪些内容可以配置,配置的默认值是什么。

将view目录下的templates文件夹重命名为pages,将message_show.html文件重命名为message_show.page。

启动程序,访问:http://localhost:8080/message/page?message=hello,发现程序正常。

thymeleaf的全部配置项如下:

No.属性详情
1spring.thymeleaf.check-template在呈现模板之前检查模板是否存在
2spring.thymeleaf.check-template-location检查模板位置是否存在
3spring.thymeleaf.prefix在构建URL时预先查看名称的前缀
4spring.thymeleaf.suffix构建URL时附加到查看名称的后缀
5spring.thymeleaf.mode应用于模板的模板模式
6spring.thymeleaf.encoding模板编码
7spring.thymeleaf.cache启用模板缓存
8spring.thymeleaf.template-resolver-order链中模板解析器的顺序
9spring.thymeleaf.view-names可以解析的视图名称,逗号分隔
10spring.thymeleaf.excluded-view-names排除的视图名称
11spring.thymeleaf.enable-spring-el-compiler是否启用spring表达式编译
12spring.thymeleaf.enabled是否启用thymeleaf模板
13spring.thymeleaf.servlet.content-typeMIME类型

三、整合静态资源

thymeleaf使用资源根路径下的static文件夹保存资源,包括css、js、图片、字体…

在view文件夹下新建static文件夹,在static文件夹下新建css、js、images文件夹,并编写一个index.html文件。在css文件夹下新建button-classic.css文件(下载button-classic.css)。

  1. <!-- index.html文件 -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8"/>
  6. <title>Thymeleaf静态资源</title>
  7. <link rel="stylesheet" type="text/css" href="/css/button-classic.css"/>
  8. <script type="text/javascript" src="/js/index.js"></script>
  9. </head>
  10. <body>
  11. <div id="image-div" style="width:300px;background: aquamarine;padding: 20px">
  12. <img src="/images/eagle.jpg" style="width:100%"/>
  13. </div>
  14. <button class="button green" id="big-change-btn">放大</button>
  15. </body>
  16. </html>

在js下新建index.js文件,编写一段javascript代码,点击按钮时放大图片。

  1. window.onload = function() {
  2. let changeBtn = document.getElementById('big-change-btn');
  3. changeBtn.addEventListener('click',function() {
  4. let imageDiv = document.getElementById('image-div');
  5. imageDiv.style.width = imageDiv.offsetWidth + 100 + 'px';
  6. })
  7. }

目录结构如下:

重新启动项目,访问:http://localhost:8080/index.html可直接看到页面。

点击按钮,图片放大,html、图片、js、css都可以正常访问。

四、访问路径支持

在thymeleaf模板页面中,可以使用@{}标记实现资源的路径定位。@{}标记是一个动态的符号,必须在动态页面中才能解析。

使用动态页面,那么请求应该由控制层跳转至显示层,新建PageAction:

  1. @Controller
  2. @RequestMapping("/message/*")
  3. public class PageAction {
  4. @GetMapping("/path")
  5. public ModelAndView pathPage() {
  6. ModelAndView mv = new ModelAndView("message/message_path"); // 跳转路径
  7. return mv;
  8. }
  9. }

在view/templates/message文件夹下新建message-path.html文件,复制上例中index.html文件的内容,将资源路径用@{}包起来,在属性前加th:(所有动态的引用都需要加th:)。

  1. <link rel="stylesheet" type="text/css" th:href="@{/css/button-classic.css}"/>
  2. <script type="text/javascript" th:src="@{/js/index.js}"></script>
  3. <img th:src="@{/images/eagle.jpg}" style="width:100%"/>

完整的message-path.html文件:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <title>Thymeleaf静态资源</title>
  6. <link rel="stylesheet" type="text/css" th:href="@{/css/button-classic.css}"/>
  7. <script type="text/javascript" th:src="@{/js/index.js}"></script>
  8. </head>
  9. <body>
  10. <div id="image-div" style="width:300px;background: aquamarine;padding: 20px">
  11. <img th:src="@{/images/eagle.jpg}" style="width:100%"/>
  12. </div>
  13. <button class="button green" id="big-change-btn">放大</button>
  14. </body>
  15. </html>

启动项目,访问:http://localhost:8080/message/path可看到与上例相同的结果。

五、读取资源文件

使用资源文件常常会伴随着国际化处理,在src/main/resource下新建一个i18n文件夹,在i18n中新建Message.properties文件。

  1. com.template.title=Thymeleaf教程
  2. com.template.content={0},好好学习哟!

新建中文版本资源文件Message_zh_CN.properties。

  1. com.template.title=Thymeleaf教程
  2. com.template.content={0},好好学习哟!

新建英文版本资源文件Message_en_US.properties。

  1. com.template.title=Thymeleaf course
  2. com.template.content={0}, study hard !

修改application.yml文件,配置资源的basename:

  1. server:
  2. port: 8080
  3. spring:
  4. messages:
  5. basename: i18n/Message

在src/main/view/templates/message文件夹下新建message_i18n.html文件,项目结构如下:

thymeleaf页面中使用#{资源名称}的形式获取资源信息,示例:

  1. <h2 th:text="'【资源】title: ' + #{com.template.title}" />

如果资源存在占位符,使用#{资源名称(参数)}的形式传入,示例:

  1. <h2 th:text="'【资源】content: ' + #{com.template.content('NicholasGUB')}" />

完整的message_i18n.html文件:

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Thymeleaf静态资源</title>
  6. </head>
  7. <body>
  8. <h2 th:text="'【资源】title: ' + #{com.template.title}" />
  9. <h2 th:text="'【资源】content: ' + #{com.template.content('NicholasGUB')}" />
  10. </body>
  11. </html>

新建ThymeleafResourceAction用于页面跳转:

  1. @Controller
  2. @RequestMapping("/i18n/*")
  3. public class ThymeleafResourceAction {
  4. @GetMapping("/page")
  5. public ModelAndView resourcePage() {
  6. ModelAndView mv = new ModelAndView("message/message_i18n");
  7. return mv;
  8. }
  9. }

启动项目,访问http://localhost:8080/i18n/page,资源加载成功。

以上代码虽然实现了资源文件的读取,但是依然存在着国际化的切换问题。可以通过一个参数lang配置区域,如果lang="zh_CN"展示中文资源,如果lang="en_US"展示英文资源。

新建ThymeleafConfig类,添加拦截器,将lang参数绑定到Locale上。

  1. @Configuration
  2. public class ThymeleafConfig implements WebMvcConfigurer {
  3. @Bean(name="localeResolver") // locale解析器
  4. public LocaleResolver getLocalResolver() { // 配置当前session对应的Locale
  5. SessionLocaleResolver resolver = new SessionLocaleResolver();
  6. resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); // 默认使用中文
  7. return resolver;
  8. }
  9. public LocaleChangeInterceptor localeChangeInterceptor() { // Locale实例拦截器
  10. LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
  11. interceptor.setParamName("lang"); // 绑定参数lang
  12. return interceptor;
  13. }
  14. @Override
  15. public void addInterceptors(InterceptorRegistry registry) { // 添加拦截器
  16. registry.addInterceptor(localeChangeInterceptor());
  17. }
  18. }

启动项目,不携带lang参数或输入http://localhost:8080/i18n/page?lang=zh_CN显示为中文,输入http://localhost:8080/i18n/page?lang=en_US切换为英文。

六、内置对象

与jsp中的内置对象类似,thymeleaf中也提供了许多内置对象。

No.内置对象描述
1${#ctx}上下文对象,可以获取其他内置对象
2${#vars}获取上下文变量
3${#locale}获取当前Locale设置
4${#request}HttpServletRequest对象实例
5${#response}HttpServletResponse对象实例
6${#session}HttpSession对象实例
7${#servletContext}ServletContext对象实例,或使用${#application}

新建InnerObjectAction类,分别使用request、session、servletContext传递属性到页面。

  1. @Controller
  2. @RequestMapping("/message/*")
  3. public class InnerObjectAction {
  4. @GetMapping("/innerObject")
  5. public String messagePage(HttpServletRequest request) {
  6. request.setAttribute("message", "request范围传递");
  7. request.getSession().setAttribute("message", "session范围传递");
  8. request.getServletContext().setAttribute("message", "application范围传递");
  9. return "message/message_inner_object";
  10. }
  11. }

在thymeleaf页面中可以使用${属性名}获取request范围的属性,使用${session.属性名}获取session范围的属性,使用${servletContext.属性名}${application.属性名}获取application范围的属性。

  1. <p th:text="'request属性:' + ${message}" />
  2. <p th:text="'session属性:' + ${session.message}" />
  3. <p th:text="'servletContext属性:' + ${application.message}" />

也可以直接使用${内置对象.getAttribute(属性名)}获取传递到页面的属性。

  1. <p th:text="'【session内置对象】:' + ${#session.getAttribute('message')}" />

在src/view/templates/message文件夹下新建message_inner_object.html页面:

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Thymeleaf内置对象</title>
  6. </head>
  7. <body>
  8. <p th:text="'request属性:' + ${message}" />
  9. <p th:text="'session属性:' + ${session.message}" />
  10. <p th:text="'servletContext属性:' + ${application.message}" />
  11. <!-- 使用内置对象获取 -->
  12. <p th:text="'【session内置对象】:' + ${#session.getAttribute('message')}" />
  13. <p th:text="'【request内置对象】远程主机:' + ${#request.getRemoteAddr()}" />
  14. <p th:text="'【session内置对象】session id:' + ${#session.getId()}" />
  15. <p th:text="'【servletContext内置对象】真实路径:' + ${#servletContext.getRealPath('/')}" />
  16. </body>
  17. </html>

启动项目,访问:http://localhost:8080/message/innerObject

七、输出对象

thymeleaf页面可以直接输出java对象,常见的做法是在controller层携带java对象跳转到页面,在页面解析对象并展示。

新建一个Person类,此处使用lombok注解生成全参构造器:

  1. @Data
  2. @AllArgsConstructor
  3. public class Person {
  4. private Integer id;
  5. private String name;
  6. private Integer age;
  7. private Double salary;
  8. private Date birthday;
  9. }

新建ShowObjectAction类,编写方法携带person对象跳转到object_show.html页面。

  1. @Controller
  2. @RequestMapping("/object/*")
  3. public class ShowObjectAction {
  4. @GetMapping("/person")
  5. public String messagePage(Model model) {
  6. Person person = new Person(1,"Annie",22,1234.56,new Date());
  7. model.addAttribute("person", person);
  8. return "message/object_show";
  9. }
  10. }

在src/main/view/templates/message下新建object_show.html页面。

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Thymeleaf输出对象</title>
  6. </head>
  7. <body>
  8. <div>
  9. <p th:text="'id: ' + ${person.id}" />
  10. <p th:text="'姓名: ' + ${person.name}" />
  11. <p th:text="'年龄: ' + ${person.age}" />
  12. <p th:text="'工资: ' + ${person.salary}" />
  13. <p th:text="'生日: ' + ${person.birthday}" />
  14. </div>
  15. </body>
  16. </html>

启动项目,访问http://localhost:8080/object/person

除了使用${对象.属性}输出外,还可以通过内置对象进行数字、日期的格式化输出,例如:

  1. <p th:text="'工资: ' + ${#numbers.formatCurrency(person.salary)}" />
  2. <p th:text="'生日: ' + ${#dates.format(person.birthday,'yyyy-MM-dd HH:mm:ss')}" />

输出一个对象的若干属性,可以采取简写形式,将对象写在父级元素上,在子元素中直接使用*{属性名}的方式输出。

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Thymeleaf输出对象</title>
  6. </head>
  7. <body>
  8. <div th:object="${person}">
  9. <p th:text="'id: ' + *{id}" />
  10. <p th:text="'姓名: ' + *{name}" />
  11. <p th:text="'年龄: ' + *{age}" />
  12. <p th:text="'工资: ' + *{#numbers.formatCurrency(salary)}" />
  13. <p th:text="'生日: ' + *{#dates.format(birthday,'yyyy-MM-dd HH:mm:ss')}" />
  14. </div>
  15. </body>
  16. </html>

八、页面逻辑

thymeleaf可以结合Spring表达式进行页面逻辑的控制。以上一节object_show.html页面为例。

判断是否成年:

  1. <div th:object="${person}">
  2. <span th:text="*{name}" />
  3. <span th:if="*{age lt 18}">未成年人</span>
  4. <span th:unless="*{age lt 18}">成年人</span>
  5. </div>

使用th:if标注的元素在条件成立时输出到页面,th:unless则相反。*{age lt 18}中的lt是Spring EL表达式中的符号,表示年龄小于18。

th:if中可以包含多个条件:

  1. <!-- 判断是否id等于1并且salary大于等于800 -->
  2. <div th:if="*{id eq 1 and salary ge 800}">
  3. 欢迎VIP用户<span th:text="*{name}" />,金额:
  4. <span th:text="*{#numbers.formatCurrency(salary)}" />
  5. </div>

使用三元运算符输出:

  1. <span th:text="*{id==1 ? '管理员':'普通用户'}" />

由于groovy“非空即真”的特性,将三元运算符简化为了二元运算符。

  1. <!--id不为空输出id,否则输出未登录-->
  2. 您的id: <span th:text="*{id?:'未登录'}" />

使用th:switch判断内容:

  1. <div th:switch="*{name}">
  2. <span th:case="Tom" >Tom</span>
  3. <span th:case="Annie" >Annie</span>
  4. <span th:case="*" >default</span>
  5. </div>

当所有条件都不满足时,使用th:case="*"进行默认处理。

完整的页面:

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Thymeleaf输出对象</title>
  6. </head>
  7. <body>
  8. <div th:object="${person}">
  9. <div>
  10. <span th:text="*{name}" />
  11. <span th:if="*{age lt 18}">未成年人</span>
  12. <span th:unless="*{age lt 18}">成年人</span>
  13. </div>
  14. <!-- 判断是否id等于1并且salary大于等于800 -->
  15. <div th:if="*{id eq 1 and salary ge 800}">
  16. 欢迎VIP用户<span th:text="*{name}" />,金额:
  17. <span th:text="*{#numbers.formatCurrency(salary)}" />
  18. </div>
  19. 您的身份是<span th:text="*{id==1 ? '管理员':'普通用户'}" />
  20. <!--id不为空输出id,否则输出未登录-->
  21. <br />您的id: <span th:text="*{id?:'未登录'}" />
  22. <div th:switch="*{name}">
  23. <span th:case="Tom" >Tom</span>
  24. <span th:case="Annie" >Annie</span>
  25. <span th:case="*" >default</span>
  26. </div>
  27. </div>
  28. </body>
  29. </html>

访问http://localhost:8080/object/person:

九、数据迭代

th:each可以在页面迭代输出集合数据。

  1. <tr th:each="person,personStat : ${personList}">
  2. <td th:text="${personStat.index}" />
  3. <td th:text="${person.id}" />
  4. </tr>

以上代码为例,person是集合中的元数据,personStat包含了一些迭代的控制标志,使用:符号遍历集合,而${personList}则是控制层传递到页面的属性,该属性的类型是列表。

新建一个IterationAction类负责传递集合到页面:

  1. @RestController
  2. @RequestMapping("/iteration/*")
  3. public class IterationAction {
  4. @GetMapping("/list")
  5. public ModelAndView list() {
  6. ModelAndView mv = new ModelAndView("message/person_list");
  7. List<Person> list = new ArrayList();
  8. for (int x = 0; x < 10; x++) {
  9. list.add(new Person(x + 1, "姓名-" + x, 10 + x, 300.32 * x, new Date()));
  10. }
  11. mv.addObject("personList", list);
  12. return mv;
  13. }
  14. }

在src/main/view/templates/message下新建一个person_list.html页面,绘制表格,填充列表数据。

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Thymeleaf迭代输出</title>
  6. </head>
  7. <body>
  8. <div>
  9. <table border="1">
  10. <thead>
  11. <tr>
  12. <th>No.</th><th>编号</th><th>姓名</th><th>年龄</th><th>工资</th><th>生日</th>
  13. </tr>
  14. </thead>
  15. <tbody>
  16. <tr th:each="person,personStat : ${personList}">
  17. <td th:text="${personStat.index}"/>
  18. <td th:text="${person.id}"/>
  19. <td th:text="${person.name}"/>
  20. <td th:text="${person.age}"/>
  21. <td th:text="${#numbers.formatCurrency(person.salary)}"/>
  22. <td th:text="${#dates.format(person.birthday,'yyyy-MM-dd HH:mm:ss')}"/>
  23. </tr>
  24. </tbody>
  25. </table>
  26. </div>
  27. </body>
  28. </html>

启动项目,访问http://localhost:8080/iteration/list

在页面迭代输出Map集合则需要借助Entry,Entry中包含了key与value值。

在IterationAction类中新建map方法,携带map集合跳转到person_map.html页面。

  1. @GetMapping("/map")
  2. public ModelAndView map() {
  3. ModelAndView mv = new ModelAndView("message/person_map");
  4. Map<Integer,Person> map = new HashMap<>();
  5. for (int x = 0; x < 10; x++) {
  6. map.put(x, new Person(x + 1, "姓名-" + x, 10 + x, 300.32 * x, new Date()));
  7. }
  8. mv.addObject("personMap", map);
  9. return mv;
  10. }

在src/main/view/templates/message下新建一个person_map.html页面。

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Thymeleaf迭代输出</title>
  6. </head>
  7. <body>
  8. <div>
  9. <table border="1">
  10. <thead>
  11. <tr>
  12. <th>No.</th><th>key</th><th>编号</th><th>姓名</th><th>年龄</th><th>工资</th><th>生日</th>
  13. </tr>
  14. </thead>
  15. <tbody>
  16. <tr th:each="personEntry,personStat : ${personMap}">
  17. <td th:text="${personStat.index}"/>
  18. <td th:text="${personEntry.key}"/>
  19. <td th:text="${personEntry.value.id}"/>
  20. <td th:text="${personEntry.value.name}"/>
  21. <td th:text="${personEntry.value.age}"/>
  22. <td th:text="${#numbers.formatCurrency(personEntry.value.salary)}"/>
  23. <td th:text="${#dates.format(personEntry.value.birthday,'yyyy-MM-dd HH:mm:ss')}"/>
  24. </tr>
  25. </tbody>
  26. </table>
  27. </div>
  28. </body>
  29. </html>

访问http://localhost:8080/iteration/map

相关文章