1. Thymeleaf 概述
Thymeleaf 是一个现代的服务器端 Java 模板引擎,适用于 Web 和独立环境。它能够处理 HTML、XML、JavaScript、CSS 甚至纯文本。
1.1 Thymeleaf 的主要特性
- 自然模板:模板文件可以直接在浏览器中打开,便于设计和调试
- Spring 集成:与 Spring Framework 完美集成
- 表达式语言:强大的表达式语言,支持变量、条件、循环等
- 布局系统:支持模板布局和片段复用
- 国际化:内置国际化支持
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 默认的静态资源目录:
- /static
- /public
- /resources
- /META-INF/resources
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 性能优化
- 启用缓存:在生产环境中启用模板缓存
- 压缩静态资源:使用压缩版本的 CSS 和 JavaScript 文件
- 延迟加载:对于大型 JavaScript 文件,使用延迟加载
10.3 安全考虑
- 转义输出:使用 th:text 而不是 th:utext 来防止 XSS 攻击
- 表单验证:在服务器端进行表单验证
- CSRF 保护:启用 Spring Security 的 CSRF 保护
10.4 开发建议
- 使用 IDE 插件:安装 Thymeleaf IDE 插件以获得更好的开发体验
- 保持一致性:遵循一致的命名规范和代码风格
- 文档化:为自定义方言和复杂模板添加注释
遵循最佳实践可以帮助你构建高质量、可维护的 Thymeleaf 模板。根据项目需求和团队情况,选择合适的最佳实践。