02、Spring Boot 4 整合 Thymeleaf 完整教程

做Web开发的时候,最烦的就是写JSP那一堆标签,什么JSTL、EL表达式,整得人头大;而且JSP还得编译,性能也不咋地;后来听说Thymeleaf这玩意儿不错,语法简洁、功能强大,还能直接在浏览器里预览,不用启动服务器;现在Spring Boot 4出来了,整合Thymeleaf更是方便得不行,自动配置给你整得明明白白的,咱今天就聊聊Spring Boot 4咋整合Thymeleaf的。

其实Thymeleaf在Spring Boot里早就支持了,你只要加个spring-boot-starter-thymeleaf依赖,基本上就能用;但是很多兄弟不知道里面的门道,也不知道咋用布局、片段、国际化这些高级功能,所以鹏磊今天就给兄弟们掰扯掰扯。

项目搭建和依赖配置

创建Maven项目

首先你得有个Maven项目,用IDEA或者Eclipse都行,或者直接用Spring Initializr生成;项目结构大概是这样:

spring-boot-thymeleaf-demo/
├── pom.xml                          # Maven配置文件
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── demo/
│   │   │               ├── Application.java          # 启动类
│   │   │               ├── controller/               # 控制器目录
│   │   │               ├── service/                  # 服务层目录
│   │   │               ├── dto/                      # 数据传输对象目录
│   │   │               └── config/                    # 配置类目录
│   │   └── resources/
│   │       ├── application.yml                       # 配置文件
│   │       ├── static/                               # 静态资源目录
│   │       │   ├── css/
│   │       │   ├── js/
│   │       │   └── images/
│   │       ├── templates/                            # Thymeleaf模板目录
│   │       │   ├── index.html                        # 首页模板
│   │       │   ├── user/                            # 用户相关模板
│   │       │   │   ├── list.html
│   │       │   │   └── detail.html
│   │       │   ├── layouts/                         # 布局模板目录
│   │       │   │   ├── base.html                    # 基础布局
│   │       │   │   └── admin.html                   # 管理后台布局
│   │       │   └── fragments/                       # 片段模板目录
│   │       │       ├── header.html                 # 头部片段
│   │       │       ├── footer.html                 # 底部片段
│   │       │       └── sidebar.html                # 侧边栏片段
│   │       └── i18n/                                # 国际化资源文件目录
│   │           ├── messages.properties              # 默认语言
│   │           ├── messages_zh_CN.properties       # 中文
│   │           └── messages_en_US.properties       # 英文
│   └── test/
│       └── java/                                     # 测试代码目录

pom.xml完整配置

这是最关键的,依赖配置不对后面全是坑;Spring Boot 4需要Java 17以上,而且Thymeleaf版本会自动管理。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- 继承Spring Boot父POM,统一管理版本 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.0</version>  <!-- Spring Boot 4.0版本 -->
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-thymeleaf-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <name>Spring Boot 4 Thymeleaf Demo</name>
    <description>Spring Boot 4整合Thymeleaf示例项目</description>
    <properties>
        <java.version>17</java.version>  <!-- Java 17以上 -->
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- Spring Boot Web Starter: 包含Spring MVC、Tomcat等 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Thymeleaf Starter: Thymeleaf模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- Thymeleaf Layout Dialect: 布局方言,支持布局和片段 -->
        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
        </dependency>
        <!-- Spring Boot Validation: 参数校验 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!-- Spring Boot DevTools: 开发工具,支持热重载 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- Lombok: 简化Java代码(可选,但强烈推荐) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Spring Boot Test: 测试支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- Spring Boot Maven插件: 打包成可执行JAR -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml配置

Thymeleaf的配置项挺多的,但是大部分用默认值就行;下面是一些常用的配置:

server:
  port: 8080  # 服务端口
  servlet:
    context-path: /  # 上下文路径
spring:
  application:
    name: spring-boot-thymeleaf-demo  # 应用名称
  # Thymeleaf配置
  thymeleaf:
    # 模板文件位置,默认是classpath:/templates/
    prefix: classpath:/templates/
    # 模板文件后缀,默认是.html
    suffix: .html
    # 模板编码,默认UTF-8
    encoding: UTF-8
    # 模板模式,默认HTML
    mode: HTML
    # 是否启用模板缓存,生产环境建议true,开发环境false
    cache: false
    # 是否检查模板是否存在
    check-template: true
    # 是否检查模板位置是否存在
    check-template-location: true
    # Content-Type,默认text/html
    servlet:
      content-type: text/html
    # 是否启用Thymeleaf视图解析器
    enabled: true
  # 静态资源配置
  web:
    resources:
      static-locations: classpath:/static/
      cache:
        period: 3600  # 静态资源缓存1小时
      chain:
        strategy:
          content:
            enabled: true  # 启用内容版本策略
  # 国际化配置
  messages:
    basename: i18n/messages  # 国际化资源文件基础名称
    encoding: UTF-8  # 编码
    cache-duration: 3600  # 缓存时间(秒)
# 日志配置
logging:
  level:
    root: INFO
    com.example.demo: DEBUG
    org.thymeleaf: DEBUG  # Thymeleaf日志

启动类配置

启动类很简单,就一个@SpringBootApplication注解:

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * Spring Boot 4 Thymeleaf应用启动类
 */
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("Spring Boot 4 Thymeleaf应用启动成功!");
    }
}

Spring Boot 4的Thymeleaf自动配置

Spring Boot 4会自动配置Thymeleaf,核心类是ThymeleafAutoConfiguration;它会自动创建SpringResourceTemplateResolverSpringTemplateEngineThymeleafViewResolver等Bean,你基本不用手动配置。

自动配置会做这些事:

  • 创建SpringResourceTemplateResolver,配置模板加载路径、编码等
  • 创建SpringTemplateEngine,自动应用SpringStandardDialect
  • 创建ThymeleafViewResolver,处理视图解析
  • 配置模板缓存、Content-Type等属性
  • 自动集成Spring的MessageSource,支持国际化
  • 自动集成Spring的转换服务(ConversionService)
// Spring Boot 4会自动配置这些Bean,你不需要手动创建
// SpringResourceTemplateResolver templateResolver = ...  // 自动创建
// SpringTemplateEngine templateEngine = ...  // 自动创建
// ThymeleafViewResolver viewResolver = ...  // 自动创建

创建第一个Thymeleaf模板

简单的HTML模板

Thymeleaf模板就是HTML文件,但是可以包含Thymeleaf表达式;先整个最简单的模板试试:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1 th:text="${message}">默认标题</h1>
    <p th:text="${'当前时间: ' + #dates.format(now, 'yyyy-MM-dd HH:mm:ss')}">当前时间</p>
    <p th:text="${'用户数量: ' + userCount}">用户数量</p>
</body>
</html>

Thymeleaf的语法用th:前缀,th:text是文本输出;注意模板里还有默认值,这样在浏览器里直接打开也能看到内容,不用启动服务器。

对应的控制器

package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.time.LocalDateTime;
/**
 * 首页控制器
 */
@Controller  // 注意这里是@Controller,不是@RestController
public class IndexController {
    /**
     * 首页
     * 返回视图名称"index",Thymeleaf会自动找templates/index.html
     */
    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("message", "欢迎使用Spring Boot 4 + Thymeleaf!");  // 往模型里添加数据
        model.addAttribute("now", LocalDateTime.now());  // 添加当前时间
        model.addAttribute("userCount", 100);  // 添加用户数量
        return "index";  // 返回视图名称,框架会自动加上前缀后缀
    }
}

Thymeleaf常用语法

1. 变量输出

<!-- 输出变量,th:text会转义HTML -->
<p th:text="${user.name}">用户名</p>
<!-- 输出变量,不转义HTML -->
<p th:utext="${user.htmlContent}">HTML内容</p>
<!-- 内联语法,直接在HTML属性中使用 -->
<p>用户名: [[${user.name}]]</p>  <!-- 转义 -->
<p>HTML: [(${user.htmlContent})]</p>  <!-- 不转义 -->
<!-- 输出变量,如果为null则显示默认值 -->
<p th:text="${user.email} ?: '未设置'">邮箱</p>
<!-- 安全导航,避免null异常 -->
<p th:text="${user?.address?.city}">城市</p>

2. 字符串操作

<!-- 字符串连接 -->
<p th:text="${user.firstName + ' ' + user.lastName}">全名</p>
<!-- 字符串替换 -->
<p th:text="${#strings.replace(user.content, 'old', 'new')}">内容</p>
<!-- 字符串截取 -->
<p th:text="${#strings.substring(user.description, 0, 50)}">描述</p>
<!-- 字符串长度 -->
<p th:text="${'描述长度: ' + #strings.length(user.description)}">长度</p>
<!-- 大小写转换 -->
<p th:text="${#strings.toUpperCase(user.name)}">大写</p>
<p th:text="${#strings.toLowerCase(user.name)}">小写</p>
<!-- 首字母大写 -->
<p th:text="${#strings.capitalize(user.name)}">首字母大写</p>
<!-- 判断是否为空 -->
<p th:if="${#strings.isEmpty(user.email)}">邮箱为空</p>
<p th:if="${#strings.isEmpty(user.email)}">邮箱不为空</p>

3. 数字格式化

<!-- 数字格式化 -->
<p th:text="${#numbers.formatDecimal(price, 0, 2)}">价格</p>  <!-- 保留两位小数 -->
<p th:text="${#numbers.formatCurrency(price)}">货币格式</p>  <!-- 货币格式 -->
<p th:text="${#numbers.formatPercent(progress)}">百分比</p>  <!-- 百分比 -->
<!-- 整数格式化 -->
<p th:text="${#numbers.formatInteger(count, 3)}">数量</p>  <!-- 至少3位数字 -->

4. 日期格式化

<!-- 日期格式化 -->
<p th:text="${#dates.format(createTime, 'yyyy-MM-dd HH:mm:ss')}">创建时间</p>
<p th:text="${#dates.format(createTime, 'yyyy年MM月dd日')}">创建日期</p>
<!-- 日期格式化(简化) -->
<p th:text="${#temporals.format(createTime, 'yyyy-MM-dd')}">创建日期</p>
<!-- 相对时间 -->
<p th:text="${#dates.format(createTime, 'yyyy-MM-dd')}">日期</p>
<p th:text="${#dates.format(createTime, 'HH:mm:ss')}">时间</p>
<!-- 日期计算 -->
<p th:text="${#dates.createToday()}">今天</p>
<p th:text="${#dates.createNow()}">现在</p>

5. 条件判断

<!-- if判断 -->
<div th:if="${user.active}">
    <p>用户已激活</p>
</div>
<!-- if-else判断 -->
<div th:if="${user.role == 'admin'}">
    <p>管理员</p>
</div>
<div th:unless="${user.role == 'admin'}">
    <p>普通用户</p>
</div>
<!-- switch-case判断 -->
<div th:switch="${user.role}">
    <p th:case="'admin'">管理员</p>
    <p th:case="'user'">普通用户</p>
    <p th:case="*">访客</p>  <!-- 默认情况 -->
</div>
<!-- 判断变量是否存在 -->
<div th:if="${user != null}">
    <p>用户存在</p>
</div>
<!-- 判断集合是否为空 -->
<div th:if="${!users.isEmpty()}">
    <p>用户列表不为空</p>
</div>

6. 循环遍历

<!-- 遍历列表 -->
<ul>
    <li th:each="user : ${users}" th:text="${user.name}">用户名</li>
</ul>
<!-- 遍历Map -->
<ul>
    <li th:each="entry : ${userMap}">
        <span th:text="${entry.key}"></span>: 
        <span th:text="${entry.value}"></span>
    </li>
</ul>
<!-- 获取循环状态 -->
<ul>
    <li th:each="user, iterStat : ${users}">
        <span th:text="${iterStat.index + 1}">序号</span>. 
        <span th:text="${user.name}">用户名</span>
        <span th:if="${iterStat.first}">(第一个)</span>
        <span th:if="${iterStat.last}">(最后一个)</span>
    </li>
</ul>
<!-- 遍历数组 -->
<ul>
    <li th:each="item : ${items}" th:text="${item}"></li>
</ul>

7. URL链接

<!-- 生成URL -->
<a th:href="@{/user/list}">用户列表</a>
<!-- 带参数的URL -->
<a th:href="@{/user/detail(id=${user.id})}">用户详情</a>
<!-- 多个参数 -->
<a th:href="@{/user/search(name=${name}, age=${age})}">搜索</a>
<!-- 路径变量 -->
<a th:href="@{/user/{id}(id=${user.id})}">用户详情</a>
<!-- 上下文路径 -->
<a th:href="@{~/static/css/style.css}">样式文件</a>
<!-- 绝对URL -->
<a th:href="@{http://www.example.com}">外部链接</a>

8. 属性设置

<!-- 设置属性 -->
<input type="text" th:value="${user.name}" />
<img th:src="@{/images/logo.png}" th:alt="${siteName}" />
<!-- 动态设置class -->
<div th:class="${user.active ? 'active' : 'inactive'}">用户</div>
<div th:class="'user-' + ${user.role}">用户</div>
<!-- 动态设置多个class -->
<div th:classappend="${user.vip ? 'vip' : ''}">用户</div>
<!-- 动态设置style -->
<div th:style="'color:' + ${user.color}">用户</div>
<!-- 条件属性 -->
<input type="checkbox" th:checked="${user.active}" />
<input type="text" th:readonly="${user.readonly}" />

表单处理

表单绑定

Thymeleaf和Spring的表单绑定集成得很好,可以自动处理验证错误、数据绑定等:

<!-- 表单 -->
<form th:action="@{/user/save}" th:object="${user}" method="post">
    <!-- 用户名输入框 -->
    <div>
        <label>用户名:</label>
        <input type="text" th:field="*{name}" />
        <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}">错误信息</span>
    </div>
    <!-- 邮箱输入框 -->
    <div>
        <label>邮箱:</label>
        <input type="email" th:field="*{email}" />
        <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}">错误信息</span>
    </div>
    <!-- 年龄输入框 -->
    <div>
        <label>年龄:</label>
        <input type="number" th:field="*{age}" />
    </div>
    <!-- 提交按钮 -->
    <button type="submit">保存</button>
</form>

对应的实体类和控制器

package com.example.demo.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Min;
import lombok.Data;
/**
 * 用户DTO
 */
@Data
public class UserDTO {
    private Long id;
    @NotBlank(message = "用户名不能为空")
    private String name;
    @Email(message = "邮箱格式不正确")
    @NotBlank(message = "邮箱不能为空")
    private String email;
    @Min(value = 1, message = "年龄必须大于0")
    private Integer age;
}
// 控制器
package com.example.demo.controller;
import com.example.demo.dto.UserDTO;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
/**
 * 用户控制器
 */
@Controller
public class UserController {
    /**
     * 显示用户表单
     */
    @GetMapping("/user/form")
    public String showForm(Model model) {
        model.addAttribute("user", new UserDTO());  // 创建空对象用于表单绑定
        return "user/form";
    }
    /**
     * 保存用户
     */
    @PostMapping("/user/save")
    public String saveUser(@Valid UserDTO user, BindingResult result, Model model) {
        if (result.hasErrors()) {
            // 有验证错误,返回表单页面
            return "user/form";
        }
        // 保存用户逻辑...
        return "redirect:/user/list";  // 重定向到列表页
    }
}

复选框和单选按钮

<!-- 复选框 -->
<div th:each="hobby : ${allHobbies}">
    <input type="checkbox" 
           th:field="*{hobbies}" 
           th:value="${hobby.id}" />
    <label th:text="${hobby.name}">爱好</label>
</div>
<!-- 单选按钮 -->
<div th:each="role : ${allRoles}">
    <input type="radio" 
           th:field="*{role}" 
           th:value="${role.id}" />
    <label th:text="${role.name}">角色</label>
</div>
<!-- 下拉框 -->
<select th:field="*{country}">
    <option value="">请选择</option>
    <option th:each="c : ${countries}" 
            th:value="${c.code}" 
            th:text="${c.name}">国家</option>
</select>

布局和片段

Thymeleaf Layout Dialect提供了布局和片段功能,可以复用模板代码。

创建基础布局

<!-- templates/layouts/base.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <meta charset="UTF-8">
    <title layout:title-pattern="$CONTENT_TITLE - $LAYOUT_TITLE">默认标题</title>
    <link rel="stylesheet" th:href="@{/css/style.css}" />
    <th:block layout:fragment="head">
        <!-- 子页面可以在这里添加额外的head内容 -->
    </th:block>
</head>
<body>
    <!-- 头部片段 -->
    <header th:replace="~{fragments/header :: header}">头部</header>
    <!-- 主要内容区域 -->
    <main layout:fragment="content">
        <!-- 子页面的内容会插入这里 -->
    </main>
    <!-- 底部片段 -->
    <footer th:replace="~{fragments/footer :: footer}">底部</footer>
    <script th:src="@{/js/main.js}"></script>
    <th:block layout:fragment="scripts">
        <!-- 子页面可以在这里添加额外的脚本 -->
    </th:block>
</body>
</html>

创建片段

<!-- templates/fragments/header.html -->
<header xmlns:th="http://www.thymeleaf.org" th:fragment="header">
    <nav>
        <a th:href="@{/}">首页</a>
        <a th:href="@{/user/list}">用户列表</a>
        <a th:href="@{/about}">关于</a>
    </nav>
</header>
<!-- templates/fragments/footer.html -->
<footer xmlns:th="http://www.thymeleaf.org" th:fragment="footer">
    <p>&copy; 2024 我的网站. All rights reserved.</p>
</footer>

使用布局

<!-- templates/index.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/base}">
<body>
    <th:block layout:fragment="content">
        <h1 th:text="${message}">欢迎</h1>
        <p>这是首页内容</p>
    </th:block>
    <th:block layout:fragment="scripts">
        <script th:src="@{/js/index.js}"></script>
    </th:block>
</body>
</html>

片段参数

片段可以接收参数,实现更灵活的复用:

<!-- 定义带参数的片段 -->
<div xmlns:th="http://www.thymeleaf.org" th:fragment="user-card(user)">
    <div class="card">
        <h3 th:text="${user.name}">用户名</h3>
        <p th:text="${user.email}">邮箱</p>
    </div>
</div>
<!-- 使用片段并传递参数 -->
<div th:replace="~{fragments/user-card :: user-card(${user})}">用户卡片</div>
<!-- 或者使用th:with定义局部变量 -->
<div th:replace="~{fragments/user-card :: user-card}" th:with="user=${currentUser}">用户卡片</div>

国际化(i18n)

Thymeleaf集成了Spring的MessageSource,支持国际化。

创建国际化资源文件

# i18n/messages.properties (默认)
welcome.message=Welcome
user.name=Name
user.email=Email
# i18n/messages_zh_CN.properties (中文)
welcome.message=欢迎
user.name=姓名
user.email=邮箱
# i18n/messages_en_US.properties (英文)
welcome.message=Welcome
user.name=Name
user.email=Email

在模板中使用

<!-- 使用消息 -->
<h1 th:text="#{welcome.message}">欢迎</h1>
<p th:text="#{user.name}">姓名</p>
<!-- 带参数的消息 -->
<!-- messages.properties: welcome.user=Welcome, {0}! -->
<p th:text="#{welcome.user(${user.name})}">欢迎, 用户名!</p>
<!-- 内联语法 -->
<p>[[#{welcome.message}]]</p>

语言切换

// 控制器中设置语言
@GetMapping("/change-locale")
public String changeLocale(@RequestParam String lang, HttpServletRequest request, HttpServletResponse response) {
    LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
    localeResolver.setLocale(request, response, new Locale(lang));
    return "redirect:/";
}
<!-- 语言切换链接 -->
<a th:href="@{/change-locale(lang='zh_CN')}">中文</a>
<a th:href="@{/change-locale(lang='en_US')}">English</a>

高级功能

1. 工具对象

Thymeleaf提供了很多工具对象,可以在模板中直接使用:

<!-- #dates: 日期工具 -->
<p th:text="${#dates.format(now, 'yyyy-MM-dd')}">日期</p>
<!-- #calendars: 日历工具 -->
<p th:text="${#calendars.format(now, 'yyyy-MM-dd')}">日期</p>
<!-- #numbers: 数字工具 -->
<p th:text="${#numbers.formatDecimal(price, 0, 2)}">价格</p>
<!-- #strings: 字符串工具 -->
<p th:text="${#strings.toUpperCase(name)}">大写</p>
<!-- #objects: 对象工具 -->
<p th:text="${#objects.nullSafe(obj, 'default')}">默认值</p>
<!-- #lists: 列表工具 -->
<p th:text="${#lists.size(users)}">列表大小</p>
<p th:if="${#lists.contains(users, currentUser)}">包含</p>
<!-- #sets: 集合工具 -->
<p th:text="${#sets.size(userSet)}">集合大小</p>
<!-- #maps: Map工具 -->
<p th:text="${#maps.size(userMap)}">Map大小</p>
<!-- #arrays: 数组工具 -->
<p th:text="${#arrays.length(items)}">数组长度</p>
<!-- #messages: 消息工具(国际化) -->
<p th:text="${#messages.msg('welcome.message')}">消息</p>
<!-- #uris: URI工具 -->
<p th:text="${#uris.escapePath(path)}">转义路径</p>
<!-- #conversions: 转换工具 -->
<p th:text="${#conversions.convert(value, String.class)}">转换</p>
<!-- #temporals: 时间工具(Java 8+) -->
<p th:text="${#temporals.format(now, 'yyyy-MM-dd')}">日期</p>

2. 表达式工具对象

<!-- #ctx: 上下文对象 -->
<p th:text="${#ctx.request.requestURI}">请求URI</p>
<p th:text="${#ctx.session.user}">Session用户</p>
<!-- #request: 请求对象 -->
<p th:text="${#request.getAttribute('attr')}">请求属性</p>
<p th:text="${#request.getParameter('param')}">请求参数</p>
<!-- #session: Session对象 -->
<p th:text="${#session.getAttribute('user')}">Session用户</p>
<!-- #servletContext: ServletContext对象 -->
<p th:text="${#servletContext.getAttribute('appName')}">应用名称</p>

3. 条件表达式

<!-- 三元运算符 -->
<p th:text="${user.active ? '已激活' : '未激活'}">状态</p>
<!-- Elvis运算符(如果为null则使用默认值) -->
<p th:text="${user.email ?: '未设置'}">邮箱</p>
<!-- 安全导航 -->
<p th:text="${user?.address?.city}">城市</p>

4. 模板注释

<!-- 普通HTML注释,会被输出到页面 -->
<!-- 这是HTML注释 -->
<!-- Thymeleaf注释,不会被输出 -->
<!--/* 这是Thymeleaf注释 */-->
<!-- 条件注释 -->
<!--/*<div th:if="${debug}">调试信息</div>*/-->

自定义配置

自定义Thymeleaf配置

如果需要自定义Thymeleaf配置,可以创建配置类:

package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.templatemode.TemplateMode;
/**
 * Thymeleaf自定义配置
 */
@Configuration
public class ThymeleafConfig {
    /**
     * 自定义模板解析器
     */
    @Bean
    public SpringResourceTemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix("classpath:/templates/");  // 模板路径
        resolver.setSuffix(".html");  // 模板后缀
        resolver.setTemplateMode(TemplateMode.HTML);  // 模板模式
        resolver.setCharacterEncoding("UTF-8");  // 编码
        resolver.setCacheable(false);  // 是否缓存(开发环境false)
        resolver.setOrder(1);  // 解析器优先级
        return resolver;
    }
    /**
     * 自定义模板引擎
     */
    @Bean
    public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver);
        // 可以添加自定义方言
        // engine.addDialect(new MyDialect());
        return engine;
    }
}

添加自定义方言

package com.example.demo.config;
import org.thymeleaf.dialect.AbstractProcessorDialect;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.standard.StandardDialect;
import java.util.HashSet;
import java.util.Set;
/**
 * 自定义Thymeleaf方言
 */
public class MyDialect extends AbstractProcessorDialect {
    public MyDialect() {
        super("My Dialect", "my", 1000);  // 名称、前缀、优先级
    }
    @Override
    public Set<IProcessor> getProcessors(String dialectPrefix) {
        Set<IProcessor> processors = new HashSet<>();
        // 添加自定义处理器
        // processors.add(new MyProcessor(dialectPrefix));
        return processors;
    }
}

最佳实践和注意事项

1. 模板组织

  • 使用布局和片段复用代码,避免重复
  • 按功能模块组织模板文件
  • 公共组件放在fragments目录
  • 布局文件放在layouts目录

2. 性能优化

  • 生产环境启用模板缓存(cache: true)
  • 开发环境禁用缓存(cache: false),方便调试
  • 避免在模板中进行复杂计算,应该在Controller中处理
  • 使用th:if而不是th:unless,性能更好

3. 安全性

  • 使用th:text而不是th:utext,避免XSS攻击
  • 如果必须使用th:utext,确保内容已经转义
  • 不要在模板中直接输出用户输入,应该先验证和转义

4. 代码规范

  • 保持模板简洁,复杂逻辑应该在Controller或Service中处理
  • 使用语义化的HTML标签
  • 合理使用Thymeleaf表达式,避免过度复杂
  • 统一使用布局,保持页面风格一致

5. 调试技巧

  • 使用th:debug属性查看变量值
  • 在模板中使用注释说明复杂逻辑
  • 使用浏览器开发者工具查看生成的HTML
  • 启用Thymeleaf日志查看模板解析过程
<!-- 调试模式,显示所有变量 -->
<div th:debug="true">
    <!-- 模板内容 -->
</div>

总结

Spring Boot 4整合Thymeleaf其实挺简单的,主要就这几步:

  1. 添加spring-boot-starter-thymeleaf依赖
  2. 创建HTML模板文件,使用th:属性
  3. Controller返回视图名称,框架自动解析
  4. 使用布局和片段复用代码
  5. 配置国际化支持多语言

Thymeleaf最大的优势就是语法简洁、功能强大,而且可以直接在浏览器预览,开发体验好;相比JSP,Thymeleaf更现代、更灵活,是Spring Boot项目首选的模板引擎;实际项目中根据需求选择就行,别想太复杂,先用起来再说。

本文章最后更新于 2025-12-08