Spring Boot Thymeleaf 教程

服务端渲染的 Web 应用开发

目录

1. Thymeleaf 概述

Thymeleaf 是一个现代的服务器端 Java 模板引擎,适用于 Web 和独立环境。它能够处理 HTML、XML、JavaScript、CSS 甚至纯文本。

1.1 Thymeleaf 的主要特性

1.2 Thymeleaf 与 JSP 的比较

特性 Thymeleaf JSP
自然模板 支持 不支持
Spring 集成 完美支持 需要额外配置
表达式语言 更强大 基础
布局系统 内置支持 需要额外框架
小提示

Thymeleaf 的设计理念是"自然模板",这意味着模板文件可以直接在浏览器中打开,便于前端开发人员设计和调试。

2. 环境搭建

2.1 添加依赖

在 Spring Boot 项目中添加 Thymeleaf 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.2 基本配置

在 application.properties 中添加 Thymeleaf 配置:

# Thymeleaf 配置
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML

2.3 目录结构

标准的 Thymeleaf 项目目录结构:

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── demo/
│   │               ├── DemoApplication.java
│   │               ├── controller/
│   │               └── config/
│   └── resources/
│       ├── templates/
│       │   ├── layout/
│       │   ├── fragments/
│       │   └── pages/
│       ├── static/
│       │   ├── css/
│       │   ├── js/
│       │   └── images/
│       └── application.properties
注意

在开发环境中,建议将 spring.thymeleaf.cache 设置为 false,以便实时查看模板更改。在生产环境中,应该设置为 true 以提高性能。

3. 基本语法

3.1 文本输出

使用 th:text 属性输出文本:

<!-- 基本文本输出 -->
<p th:text="${message}">Default message</p>

<!-- 使用 th:utext 输出 HTML -->
<div th:utext="${htmlContent}">Default HTML content</div>

3.2 条件语句

使用 th:if 和 th:unless 进行条件判断:

<!-- 条件判断 -->
<div th:if="${user != null}">
    <p th:text="${user.name}">User name</p>
</div>

<div th:unless="${user != null}">
    <p>Please login</p>
</div>

<!-- 多条件判断 -->
<div th:switch="${user.role}">
    <p th:case="'ADMIN'">Administrator</p>
    <p th:case="'USER'">Regular User</p>
    <p th:case="*">Unknown Role</p>
</div>

3.3 循环语句

使用 th:each 进行循环:

<!-- 基本循环 -->
<ul>
    <li th:each="item : ${items}" th:text="${item.name}">Item name</li>
</ul>

<!-- 带状态的循环 -->
<table>
    <tr th:each="user, stat : ${users}">
        <td th:text="${stat.index}">0</td>
        <td th:text="${stat.count}">1</td>
        <td th:text="${stat.size}">2</td>
        <td th:text="${stat.current}">3</td>
        <td th:text="${stat.even}">4</td>
        <td th:text="${stat.odd}">5</td>
        <td th:text="${stat.first}">6</td>
        <td th:text="${stat.last}">7</td>
        <td th:text="${user.name}">User name</td>
    </tr>
</table>

3.4 属性设置

使用 th:attr 设置属性:

<!-- 设置单个属性 -->
<input type="text" th:value="${user.name}" />

<!-- 设置多个属性 -->
<img th:attr="src=@{${imageUrl}}, alt=${imageAlt}" />

<!-- 追加属性值 -->
<div class="base" th:classappend="${isActive ? 'active' : 'inactive'}">
    Content
</div>
小提示

在循环中使用 stat 变量可以获取循环的状态信息,如索引、计数、是否第一个/最后一个等。

4. 布局模板

4.1 基本布局

创建一个基本的布局模板:

<!-- layout/main.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:replace="${title}">Default Title</title>
    <link rel="stylesheet" th:href="@{/css/main.css}">
</head>
<body>
    <header>
        <nav th:replace="fragments/nav :: nav"></nav>
    </header>
    
    <main>
        <div th:replace="${content}">
            Page content goes here
        </div>
    </main>
    
    <footer th:replace="fragments/footer :: footer"></footer>
    
    <script th:src="@{/js/main.js}"></script>
</body>
</html>

4.2 使用布局

在页面中使用布局模板:

<!-- pages/home.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home Page</title>
</head>
<body>
    <div th:fragment="content">
        <h1>Welcome to Home Page</h1>
        <p>This is the home page content.</p>
    </div>
</body>
</html>

4.3 布局配置

在 Spring Boot 中配置布局:

@Configuration
public class ThymeleafConfig {
    
    @Bean
    public SpringResourceTemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setCacheable(false);
        return resolver;
    }
    
    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver());
        engine.setEnableSpringELCompiler(true);
        return engine;
    }
}
注意

布局模板可以帮助保持页面结构的一致性,减少代码重复。建议将常用的页面元素(如导航栏、页脚)放在单独的片段文件中。

5. 表单处理

5.1 基本表单

创建一个基本的表单:

<!-- 表单模板 -->
<form th:action="@{/users/save}" method="post" th:object="${user}">
    <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" class="form-control" id="username" 
               th:field="*{username}" />
        <span class="error" th:if="${#fields.hasErrors('username')}" 
              th:errors="*{username}"></span>
    </div>
    
    <div class="form-group">
        <label for="email">Email:</label>
        <input type="email" class="form-control" id="email" 
               th:field="*{email}" />
        <span class="error" th:if="${#fields.hasErrors('email')}" 
              th:errors="*{email}"></span>
    </div>
    
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

5.2 表单验证

在控制器中处理表单验证:

@Controller
@RequestMapping("/users")
public class UserController {
    
    @PostMapping("/save")
    public String saveUser(@Valid User user, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "users/form";
        }
        
        userService.save(user);
        return "redirect:/users";
    }
}

5.3 复选框和单选按钮

处理复选框和单选按钮:

<!-- 复选框 -->
<div class="form-group">
    <label>Interests:</label>
    <div th:each="interest : ${allInterests}">
        <label>
            <input type="checkbox" th:field="*{interests}" 
                   th:value="${interest.id}" />
            <span th:text="${interest.name}">Interest</span>
        </label>
    </div>
</div>

<!-- 单选按钮 -->
<div class="form-group">
    <label>Gender:</label>
    <div th:each="gender : ${genders}">
        <label>
            <input type="radio" th:field="*{gender}" 
                   th:value="${gender}" />
            <span th:text="${gender}">Gender</span>
        </label>
    </div>
</div>
小提示

使用 th:field 属性可以自动绑定表单字段与模型属性,并处理验证错误。这是 Thymeleaf 的一个强大特性。

6. 国际化

6.1 消息配置

配置国际化消息文件:

# messages.properties (默认)
welcome.message=Welcome to our application
user.name=Name
user.email=Email

# messages_zh_CN.properties (中文)
welcome.message=欢迎使用我们的应用
user.name=姓名
user.email=邮箱

# messages_en_US.properties (英文)
welcome.message=Welcome to our application
user.name=Name
user.email=Email

6.2 使用国际化消息

在模板中使用国际化消息:

<!-- 使用 #{} 语法 -->
<h1 th:text="#{welcome.message}">Welcome</h1>

<!-- 带参数的消息 -->
<p th:text="#{user.greeting(${user.name})}">Hello, User!</p>

<!-- 表单标签 -->
<label th:text="#{user.name}">Name:</label>
<input type="text" th:field="*{name}" />

6.3 语言切换

实现语言切换功能:

@Controller
public class LocaleController {
    
    @GetMapping("/change-language")
    public String changeLanguage(@RequestParam String lang, 
                               HttpSession session) {
        session.setAttribute("lang", lang);
        return "redirect:/";
    }
}
<!-- 语言切换链接 -->
<div class="language-switcher">
    <a th:href="@{/change-language(lang='zh_CN')}">中文</a>
    <a th:href="@{/change-language(lang='en_US')}">English</a>
</div>
注意

国际化消息文件应该放在 resources 目录下,文件名格式为 messages_语言代码_国家代码.properties。

7. 静态资源

7.1 资源目录

Spring Boot 默认的静态资源目录:

7.2 引用静态资源

在模板中引用静态资源:

<!-- CSS 文件 -->
<link rel="stylesheet" th:href="@{/css/style.css}">

<!-- JavaScript 文件 -->
<script th:src="@{/js/main.js}"></script>

<!-- 图片 -->
<img th:src="@{/images/logo.png}" alt="Logo">

7.3 资源版本控制

使用 Spring 的资源版本控制:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
                .resourceChain(true)
                .addResolver(new VersionResourceResolver()
                        .addContentVersionStrategy("/**"));
    }
}
小提示

使用 @{...} 语法可以自动处理上下文路径,使资源引用更加灵活。

8. 片段复用

8.1 定义片段

创建一个包含片段的模板:

<!-- fragments/nav.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <nav th:fragment="nav">
        <ul>
            <li><a th:href="@{/}">Home</a></li>
            <li><a th:href="@{/about}">About</a></li>
            <li><a th:href="@{/contact}">Contact</a></li>
        </ul>
    </nav>
</body>
</html>

8.2 使用片段

在页面中使用片段:

<!-- 使用 th:replace -->
<nav th:replace="fragments/nav :: nav"></nav>

<!-- 使用 th:insert -->
<div th:insert="fragments/nav :: nav"></div>

8.3 片段参数

向片段传递参数:

<!-- 定义带参数的片段 -->
<div th:fragment="alert(type, message)">
    <div th:class="${'alert alert-' + type}">
        <span th:text="${message}">Message</span>
    </div>
</div>

<!-- 使用带参数的片段 -->
<div th:replace="fragments/alert :: alert('success', 'Operation completed successfully')">
</div>
注意

th:replace 会完全替换当前元素,而 th:insert 会保留当前元素并将片段插入其中。选择合适的方式取决于你的需求。

9. 自定义方言

9.1 创建自定义属性

创建一个自定义属性处理器:

public class DataTableAttributeProcessor extends AbstractAttributeProcessor {
    
    public DataTableAttributeProcessor() {
        super("datatable");
    }
    
    @Override
    protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
                           AttributeName attributeName, String attributeValue,
                           IElementTagStructureHandler structureHandler) {
        // 处理逻辑
        String tableId = attributeValue;
        structureHandler.setAttribute("class", "table table-striped");
        structureHandler.setAttribute("id", tableId);
    }
}

9.2 注册自定义方言

在 Spring Boot 中注册自定义方言:

@Configuration
public class ThymeleafConfig {
    
    @Bean
    public IDialect customDialect() {
        Set<IProcessor> processors = new HashSet<>();
        processors.add(new DataTableAttributeProcessor());
        return new CustomDialect(processors);
    }
}

9.3 使用自定义属性

在模板中使用自定义属性:

<table th:datatable="users-table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="user : ${users}">
            <td th:text="${user.name}"></td>
            <td th:text="${user.email}"></td>
        </tr>
    </tbody>
</table>
小提示

自定义方言可以帮助你创建更符合项目需求的模板语法,提高代码的可读性和可维护性。

10. 最佳实践

10.1 模板组织

10.2 性能优化

10.3 安全考虑

10.4 开发建议

笔记

遵循最佳实践可以帮助你构建高质量、可维护的 Thymeleaf 模板。根据项目需求和团队情况,选择合适的最佳实践。

返回首页