当前位置: 首页 > news >正文

Spring Boot 3 + Thymeleaf 基础教程

Spring Boot 3 + Thymeleaf 基础教程

系统学习Spring Boot与Thymeleaf的整合开发。

本教程基于最新的技术栈,包括JDK 25和Spring Boot 3.5.6,通过理论与实践相结合的方式,帮助你快速掌握这一流行的Java Web开发组合。

第1章:环境搭建与入门示例

1.1 理论基础

Spring Boot是Spring框架的简化版,它简化了Spring应用的初始搭建和开发过程。

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

两者结合的优势:

  • 无需手动配置视图解析器
  • 强大的模板引擎支持
  • 自然模板特性,模板文件可直接在浏览器中打开
  • 与Spring生态完美集成

1.2 开发环境准备

  • JDK 25
  • Maven 3.9+ 或 Gradle 8.5+
  • IDE(推荐IntelliJ IDEA 2025或Eclipse 2025)
  • 浏览器(Chrome/Firefox最新版)

1.3 第一个Spring Boot + Thymeleaf应用

开发思路
  1. 创建Spring Boot项目并添加必要依赖
  2. 配置基本的应用属性
  3. 创建控制器类处理请求
  4. 创建Thymeleaf模板页面
  5. 运行应用并测试
开发过程

步骤1:创建Maven项目并配置pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><!-- 模型版本 --><modelVersion>4.0.0</modelVersion><!-- 父项目,继承Spring Boot默认配置 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.5.6</version><relativePath/> <!-- lookup parent from repository --></parent><!-- 项目基本信息 --><groupId>com.lihaozhe</groupId><artifactId>springboot-thymeleaf-demo</artifactId><version>0.0.1</version><name>springboot-thymeleaf-demo</name><description>Spring Boot + Thymeleaf Demo Application</description><!-- JDK版本配置 --><properties><java.version>25</java.version></properties><!-- 项目依赖 --><dependencies><!-- Spring Boot Web依赖,包含Spring MVC等Web开发所需组件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Thymeleaf依赖,模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- Spring Boot DevTools,支持热部署等开发便利特性 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><!-- Spring Boot测试依赖 --><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></plugin></plugins></build>
</project>

步骤2:创建应用主类

package com.lihaozhe.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Spring Boot应用主类* @SpringBootApplication 注解是一个组合注解,包含:* - @Configuration: 标记此类为配置类* - @EnableAutoConfiguration: 启用Spring Boot的自动配置机制* - @ComponentScan: 启用组件扫描,扫描当前包及其子包下的组件*/
@SpringBootApplication
public class SpringbootThymeleafDemoApplication {// 应用入口方法public static void main(String[] args) {// 启动Spring Boot应用SpringApplication.run(SpringbootThymeleafDemoApplication.class, args);}}

步骤3:创建控制器

package com.lihaozhe.demo.controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;/*** 控制器类,处理HTTP请求* @Controller 注解标记此类为Spring MVC控制器*/
@Controller
public class HelloController {/*** 处理根路径("/")的GET请求* @GetMapping 注解用于映射HTTP GET请求* * @param model 模型对象,用于在控制器和视图之间传递数据* @return 视图名称,Thymeleaf将根据此名称查找对应的模板文件*/@GetMapping("/")public String index(Model model) {// 向模型中添加属性,键为"message",值为"Hello, Thymeleaf!"model.addAttribute("message", "Hello, Thymeleaf!");// 返回视图名称"index",Thymeleaf会查找src/main/resources/templates/index.htmlreturn "index";}/*** 处理"/greet"路径的GET请求* * @param model 模型对象* @return 视图名称"greet"*/@GetMapping("/greet")public String greet(Model model) {// 向模型添加多个属性model.addAttribute("name", "Spring Boot");model.addAttribute("version", "3.5.6");return "greet";}
}

步骤4:创建Thymeleaf模板

首先创建index.html:

<!DOCTYPE html>
<!-- Thymeleaf模板需要在html标签中添加命名空间声明xmlns:th="http://www.thymeleaf.org" 用于支持Thymeleaf属性
-->
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Spring Boot + Thymeleaf</title>
</head>
<body><h1>Welcome to Spring Boot with Thymeleaf</h1><!-- th:text 属性用于设置元素的文本内容${message} 是变量表达式,用于获取模型中的message属性值--><p th:text="${message}">Default message</p><!-- 普通HTML链接,跳转到/greet路径 --><p><a href="/greet">Go to Greet Page</a></p>
</body>
</html>

然后创建greet.html:

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Greet Page</title>
</head>
<body><h1>Greetings!</h1><!-- 使用Thymeleaf表达式拼接字符串 --><p th:text="'Hello from ' + ${name} + ' version ' + ${version}">Hello from framework</p><!-- 使用th:href创建链接,@{/}是链接表达式,用于生成上下文相关的URL这样即使应用部署在非根路径下,链接也能正确生成--><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

步骤5:配置应用属性(可选)

application.properties

# 服务器端口配置,默认为8080
server.port=8080# Thymeleaf配置
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 开发时关闭缓存,修改模板后无需重启应用
spring.thymeleaf.cache=false
# 模板模式
spring.thymeleaf.mode=HTML
# 模板前缀,默认就是classpath:/templates/
spring.thymeleaf.prefix=classpath:/templates/
# 模板后缀,默认就是.html
spring.thymeleaf.suffix=.html

1.4 运行与测试

  1. 运行SpringbootThymeleafDemoApplication类的main方法
  2. 打开浏览器访问 http://localhost:8080
  3. 你应该能看到首页显示"Hello, Thymeleaf!"
  4. 点击链接进入greet页面,应该能看到"Hello from Spring Boot version 3.5.6"
  5. 点击"Back to Home"可以返回首页

1.5 章节总结

本章我们学习了:

  • Spring Boot与Thymeleaf的基本概念
  • 如何搭建Spring Boot + Thymeleaf开发环境
  • 创建简单的控制器处理HTTP请求
  • 使用Thymeleaf模板展示数据
  • 基本的Thymeleaf表达式语法

在下一章中,我们将深入学习Thymeleaf的模板语法和更多特性。

第2章:Thymeleaf模板语法详解

2.1 理论基础

Thymeleaf的核心是其模板引擎和表达式语言。它使用特定的属性(以th:前缀标识)来增强HTML,使静态模板能够动态展示数据。Thymeleaf的表达式语言支持变量访问、字符串操作、集合处理等功能。

主要表达式类型:

  • 变量表达式:${...}
  • 选择变量表达式:*{...}
  • 链接表达式:@{...}
  • 文本外部化表达式:#{...}
  • 片段表达式:~{...}

2.2 开发思路

  1. 创建展示各种表达式用法的控制器
  2. 设计包含不同Thymeleaf语法的模板页面
  3. 展示数据绑定、条件判断、循环等核心功能
  4. 测试并验证各种语法的效果

2.3 实践示例

步骤1:创建数据模型类

package com.lihaozhe.demo.model;import java.util.Date;/*** 用户实体类,用于演示Thymeleaf数据绑定*/
public class User {// 用户名private String username;// 邮箱private String email;// 年龄private int age;// 注册日期private Date registrationDate;// 是否为VIP用户private boolean vip;// 无参构造函数public User() {}// 带参构造函数public User(String username, String email, int age, Date registrationDate, boolean vip) {this.username = username;this.email = email;this.age = age;this.registrationDate = registrationDate;this.vip = vip;}// Getter和Setter方法public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Date getRegistrationDate() {return registrationDate;}public void setRegistrationDate(Date registrationDate) {this.registrationDate = registrationDate;}public boolean isVip() {return vip;}public void setVip(boolean vip) {this.vip = vip;}
}

步骤2:创建演示控制器

package com.lihaozhe.demo.controller;import com.lihaozhe.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** Thymeleaf语法演示控制器*/
@Controller
public class ThymeleafSyntaxController {/*** 展示变量表达式和基本语法*/@GetMapping("/syntax/variables")public String variables(Model model) {// 添加基本类型变量model.addAttribute("username", "John Doe");model.addAttribute("age", 30);model.addAttribute("isStudent", false);// 添加对象类型变量User user = new User("Jane Smith", "jane@lihaozhe.com", 28, new Date(), true);model.addAttribute("user", user);return "syntax/variables";}/*** 展示集合和循环语法*/@GetMapping("/syntax/collections")public String collections(Model model) {// 创建用户列表List<User> users = new ArrayList<>();users.add(new User("Alice", "alice@lihaozhe.com", 25, new Date(), false));users.add(new User("Bob", "bob@lihaozhe.com", 32, new Date(), true));users.add(new User("Charlie", "charlie@lihaozhe.com", 45, new Date(), true));// 创建产品映射Map<String, Double> products = new HashMap<>();products.put("Laptop", 999.99);products.put("Smartphone", 699.99);products.put("Tablet", 299.99);model.addAttribute("users", users);model.addAttribute("products", products);return "syntax/collections";}/*** 展示条件判断语法*/@GetMapping("/syntax/conditions")public String conditions(Model model) {model.addAttribute("score", 85);model.addAttribute("isLoggedIn", true);model.addAttribute("role", "ADMIN");return "syntax/conditions";}/*** 展示字符串操作和工具类*/@GetMapping("/syntax/strings")public String strings(Model model) {model.addAttribute("message", "Hello Thymeleaf");model.addAttribute("username", "  john_doe  ");model.addAttribute("currentDate", new Date());return "syntax/strings";}
}

步骤3:创建模板文件

首先创建变量表达式演示页面(variables.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf Variables</title>
</head>
<body><h1>Variable Expressions Demo</h1><h2>Basic Variables</h2><!-- 基本变量展示 --><p>Username: <span th:text="${username}">Default Name</span></p><p>Age: <span th:text="${age}">0</span></p><p>Is Student: <span th:text="${isStudent}">false</span></p><h2>Object Properties</h2><!-- 对象属性访问 --><p>User Name: <span th:text="${user.username}">Name</span></p><p>Email: <span th:text="${user.email}">Email</span></p><p>Age: <span th:text="${user.age}">Age</span></p><p>VIP Status: <span th:text="${user.vip}">VIP</span></p><h2>Selection Variable Expressions</h2><!-- 选择变量表达式 *{...}配合th:object使用,可以简化对象属性访问--><div th:object="${user}"><p>Selected User Name: <span th:text="*{username}">Name</span></p><p>Selected Email: <span th:text="*{email}">Email</span></p><p>Selected Registration Date: <span th:text="*{registrationDate}">Date</span></p></div><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

创建集合和循环演示页面(collections.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf Collections</title><style>table { border-collapse: collapse; width: 60%; }table, th, td { border: 1px solid #333; }th, td { padding: 8px; text-align: left; }th { background-color: #f2f2f2; }</style>
</head>
<body><h1>Collections and Loops Demo</h1><h2>User List (th:each)</h2><!-- th:each 用于遍历集合语法: variable,status : ${collection}status包含index(从0开始), count(从1开始), size, even/odd, first, last等属性--><table><tr><th>#</th><th>Username</th><th>Email</th><th>Age</th><th>VIP</th></tr><tr th:each="user, userStat : ${users}"><td th:text="${userStat.count}">1</td><td th:text="${user.username}">Name</td><td th:text="${user.email}">Email</td><td th:text="${user.age}">Age</td><td th:text="${user.vip}">VIP</td></tr></table><h2>Product Map</h2><!-- 遍历Map集合 --><ul><li th:each="product : ${products}"><span th:text="${product.key}">Product</span>: $<span th:text="${product.value}">0.00</span></li></ul><h2>List Information</h2><!-- 集合信息展示 --><p>Total users: <span th:text="${users.size()}">0</span></p><p>First user: <span th:text="${users[0].username}">First</span></p><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

创建条件判断演示页面(conditions.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf Conditions</title>
</head>
<body><h1>Conditionals Demo</h1><h2>th:if and th:unless</h2><!-- th:if: 当表达式为真时显示元素th:unless: 当表达式为假时显示元素(与th:if相反)--><p th:if="${isLoggedIn}">Welcome! You are logged in.</p><p th:unless="${isLoggedIn}">Please log in to continue.</p><h2>Score Evaluation</h2><!-- 多条件判断 --><div th:if="${score >= 90}"><p>Excellent! Your score is <span th:text="${score}">0</span>.</p></div><div th:unless="${score >= 90}" th:if="${score >= 70}"><p>Good! Your score is <span th:text="${score}">0</span>.</p></div><div th:unless="${score >= 70}"><p>Needs improvement. Your score is <span th:text="${score}">0</span>.</p></div><h2>th:switch and th:case</h2><!-- 多分支判断,类似switch-case --><div th:switch="${role}"><p th:case="'ADMIN'">You have administrator privileges.</p><p th:case="'USER'">You have user privileges.</p><p th:case="*">You have guest privileges.</p> <!-- default case --></div><h2>Conditional Attributes</h2><!-- 条件性地设置属性 --><div th:attr="style=${score >= 90 ? 'color: green;' : 'color: black;'}">This text's color depends on the score.</div><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

创建字符串操作演示页面(strings.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf Strings</title>
</head>
<body><h1>String Operations and Utilities</h1><h2>String Manipulation</h2><!-- 字符串连接 --><p>Concatenation: <span th:text="'Message: ' + ${message}">Message</span></p><!-- 字符串工具类 #strings 的使用 --><p>Uppercase: <span th:text="${#strings.toUpperCase(message)}">MESSAGE</span></p><p>Lowercase: <span th:text="${#strings.toLowerCase(message)}">message</span></p><p>Length: <span th:text="${#strings.length(message)}">0</span></p><p>Substring: <span th:text="${#strings.substring(message, 0, 5)}">Sub</span></p><!-- 字符串替换和修剪 --><p>Original username: <span th:text="${username}">username</span></p><p>Trimmed: <span th:text="${#strings.trim(username)}">trimmed</span></p><p>Replaced: <span th:text="${#strings.replace(message, 'Thymeleaf', 'Templates')}">Replaced</span></p><h2>Date Formatting</h2><!-- 日期格式化 #dates 工具类 --><p>Original date: <span th:text="${currentDate}">Date</span></p><p>Formatted date: <span th:text="${#dates.format(currentDate, 'yyyy-MM-dd')}">2023-01-01</span></p><p>Full date: <span th:text="${#dates.format(currentDate, 'yyyy-MM-dd HH:mm:ss')}">2023-01-01 12:00:00</span></p><p>Year: <span th:text="${#dates.year(currentDate)}">2023</span></p><p>Month: <span th:text="${#dates.month(currentDate)}">1</span></p><h2>Number Formatting</h2><!-- 数字格式化 #numbers 工具类 --><p>Formatted number: <span th:text="${#numbers.formatDecimal(12345.678, 0, 'COMMA', 2, 'POINT')}">12,345.68</span></p><h2>Boolean Utilities</h2><!-- 布尔值处理 #bools 工具类 --><p>Is true: <span th:text="${#bools.isTrue(true)}">true</span></p><p>Is false: <span th:text="${#bools.isFalse(true)}">false</span></p><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

2.4 运行与测试

  1. 启动应用
  2. 访问以下URL测试不同的语法特性:
    • http://localhost:8080/syntax/variables - 变量表达式
    • http://localhost:8080/syntax/collections - 集合和循环
    • http://localhost:8080/syntax/conditions - 条件判断
    • http://localhost:8080/syntax/strings - 字符串操作

2.5 章节总结

本章我们学习了Thymeleaf的核心语法:

  • 变量表达式和选择变量表达式的使用
  • 如何遍历集合和使用循环
  • 条件判断语句的应用
  • 字符串、日期和数字的格式化
  • Thymeleaf内置工具类的使用

这些基础语法是使用Thymeleaf开发动态页面的核心,掌握它们将为后续开发打下坚实基础。

第3章:表单处理与数据验证

3.1 理论基础

在Web应用中,表单是用户与系统交互的主要方式。Spring Boot结合Thymeleaf提供了强大的表单处理能力,包括:

  • 表单数据与Java对象的双向绑定
  • 服务器端数据验证
  • 错误信息展示
  • 表单重定向与数据保留

关键技术点:

  • @ModelAttribute:将请求参数绑定到模型对象
  • @Valid:启用数据验证
  • BindingResult:存储验证结果和错误信息
  • Thymeleaf表单属性:th:objectth:fieldth:errors

3.2 开发思路

  1. 创建带验证注解的实体类
  2. 开发处理表单GET和POST请求的控制器
  3. 设计包含表单的Thymeleaf模板
  4. 实现数据验证和错误信息展示
  5. 处理表单提交成功的场景

3.3 实践示例

步骤1:添加必要依赖(如果尚未添加)

pom.xml与第1章相同,Spring Boot Web Starter已包含validation依赖

步骤2:创建带验证的实体类

package com.lihaozhe.demo.model;import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;/*** 带数据验证的用户实体类* 使用jakarta.validation.constraints包中的注解进行数据验证*/
public class ValidatedUser {// 用户名:不能为空,长度在3-20之间@NotBlank(message = "Username is required")@Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters")private String username;// 邮箱:不能为空,且必须是合法的邮箱格式@NotBlank(message = "Email is required")@Email(message = "Please provide a valid email address")private String email;// 密码:不能为空,长度至少6位@NotBlank(message = "Password is required")@Size(min = 6, message = "Password must be at least 6 characters")private String password;// 全名:不能为空@NotBlank(message = "Full name is required")private String fullName;// 出生日期:必须是过去的日期private LocalDate dateOfBirth;// 无参构造函数public ValidatedUser() {}// Getter和Setter方法public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getFullName() {return fullName;}public void setFullName(String fullName) {this.fullName = fullName;}public LocalDate getDateOfBirth() {return dateOfBirth;}public void setDateOfBirth(LocalDate dateOfBirth) {this.dateOfBirth = dateOfBirth;}
}

步骤3:创建表单处理控制器

package com.lihaozhe.demo.controller;import com.lihaozhe.demo.model.ValidatedUser;
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.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;import java.time.LocalDate;
import java.time.Period;/*** 表单处理控制器* 处理用户注册表单的展示、提交和验证*/
@Controller
public class FormController {/*** 显示注册表单* @param model 模型对象* @return 表单页面视图名*/@GetMapping("/register")public String showRegistrationForm(Model model) {// 向模型中添加一个空的ValidatedUser对象,用于表单绑定model.addAttribute("user", new ValidatedUser());return "form/register";}/*** 处理表单提交* @param user 绑定了表单数据的用户对象,@Valid注解启用数据验证* @param bindingResult 存储验证结果和错误信息* @param model 模型对象* @return 成功页面或表单页面(验证失败时)*/@PostMapping("/register")public String processRegistration(@Valid @ModelAttribute("user") ValidatedUser user,BindingResult bindingResult,Model model) {// 自定义验证:检查年龄是否大于等于18岁if (user.getDateOfBirth() != null) {LocalDate today = LocalDate.now();int age = Period.between(user.getDateOfBirth(), today).getYears();if (age < 18) {// 添加自定义错误信息bindingResult.rejectValue("dateOfBirth", "error.user", "You must be at least 18 years old");}} else {// 如果未填写出生日期,添加错误信息bindingResult.rejectValue("dateOfBirth", "error.user", "Date of birth is required");}// 检查是否有验证错误if (bindingResult.hasErrors()) {// 有错误,返回表单页面,显示错误信息return "form/register";}// 没有错误,将用户信息添加到模型,用于成功页面展示model.addAttribute("user", user);// 重定向到成功页面,避免表单重复提交return "redirect:/registration-success";}/*** 显示注册成功页面* @param model 模型对象* @return 成功页面视图名*/@GetMapping("/registration-success")public String showRegistrationSuccess(Model model) {// 如果用户信息不存在,重定向到注册页面if (!model.containsAttribute("user")) {return "redirect:/register";}return "form/success";}
}

步骤4:创建表单页面

register.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>User Registration</title><style>.form-group {margin-bottom: 15px;}label {display: inline-block;width: 120px;}input[type="text"],input[type="email"],input[type="password"],input[type="date"] {width: 250px;padding: 5px;}.error {color: red;margin-left: 125px;font-size: 0.9em;}.error-message {color: red;margin: 10px 0;padding: 10px;border: 1px solid #ff0000;background-color: #ffe6e6;}button {margin-left: 125px;padding: 8px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}</style>
</head>
<body><h1>User Registration</h1><!-- 显示全局错误信息 --><div th:if="${#fields.hasGlobalErrors()}" class="error-message"><p th:each="error : ${#fields.globalErrors()}" th:text="${error}">Error message</p></div><!-- 表单标签,th:object指定表单绑定的模型对象th:action指定表单提交的URL--><form th:object="${user}" th:action="@{/register}" method="post"><!-- 用户名输入框 --><div class="form-group"><label for="username">Username:</label><!-- th:field用于表单字段绑定,等价于:name="username" id="username" value="${user.username}"--><input type="text" th:field="*{username}" /><!-- 显示字段验证错误 --><p th:if="${#fields.hasErrors('username')}" th:errors="*{username}" class="error">Username error</p></div><!-- 邮箱输入框 --><div class="form-group"><label for="email">Email:</label><input type="email" th:field="*{email}" /><p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="error">Email error</p></div><!-- 密码输入框 --><div class="form-group"><label for="password">Password:</label><input type="password" th:field="*{password}" /><p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="error">Password error</p></div><!-- 全名输入框 --><div class="form-group"><label for="fullName">Full Name:</label><input type="text" th:field="*{fullName}" /><p th:if="${#fields.hasErrors('fullName')}" th:errors="*{fullName}" class="error">Full name error</p></div><!-- 出生日期输入框 --><div class="form-group"><label for="dateOfBirth">Date of Birth:</label><input type="date" th:field="*{dateOfBirth}" /><p th:if="${#fields.hasErrors('dateOfBirth')}" th:errors="*{dateOfBirth}" class="error">Date of birth error</p></div><!-- 提交按钮 --><div class="form-group"><button type="submit">Register</button></div></form><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

步骤5:创建注册成功页面

success.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Registration Successful</title><style>.success {color: green;margin: 20px 0;}.user-info {margin: 20px;padding: 15px;border: 1px solid #ddd;border-radius: 4px;}</style>
</head>
<body><h1 class="success">Registration Successful!</h1><div class="user-info"><h2>Your Information:</h2><p><strong>Username:</strong> <span th:text="${user.username}">username</span></p><p><strong>Email:</strong> <span th:text="${user.email}">email</span></p><p><strong>Full Name:</strong> <span th:text="${user.fullName}">full name</span></p><p><strong>Date of Birth:</strong> <span th:text="${#dates.format(user.dateOfBirth, 'yyyy-MM-dd')}">date</span></p></div><p><a th:href="@{/register}">Register Another User</a></p><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

3.4 运行与测试

  1. 启动应用
  2. 访问 http://localhost:8080/register
  3. 测试场景:
    • 提交空表单,验证所有必填项错误
    • 输入不符合规则的数据(如短密码、无效邮箱)
    • 输入出生日期为未来日期或使年龄小于18岁
    • 填写所有有效信息,提交后查看成功页面

3.5 章节总结

本章我们学习了:

  • 使用JSR-380验证注解进行数据验证
  • Thymeleaf表单绑定的核心属性(th:object, th:field)
  • 错误信息的展示方法
  • 自定义验证逻辑的实现
  • 表单提交后的重定向处理

这些技能对于开发用户交互友好的Web应用至关重要,能够确保数据的有效性并提供清晰的错误反馈。

第4章:Thymeleaf模板布局与片段复用

4.1 理论基础

在Web开发中,页面布局的一致性和代码复用是重要的设计原则。Thymeleaf提供了强大的模板布局功能,主要包括:

  • 片段(Fragments):可以被复用的页面组件
  • 布局模板:定义页面的整体结构
  • 模板继承:子页面可以继承父模板并填充内容
  • 片段包含与替换:在页面中引入或替换片段

核心属性:

  • th:fragment:定义可复用的片段
  • th:include/th:replace/th:insert:引入片段
  • th:remove:移除元素

4.2 开发思路

  1. 创建基础布局模板,包含页面公共部分(头部、导航、底部等)
  2. 定义可复用的UI组件片段(如通知、按钮等)
  3. 创建多个子页面,继承基础布局并填充特有内容
  4. 实现片段的条件性引入和动态替换

4.3 实践示例

步骤1:创建布局模板

main-layout.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title th:text="${pageTitle != null ? pageTitle + ' - My Site' : 'My Site'}">My Site</title><!-- 公共CSS --><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: Arial, sans-serif;line-height: 1.6;color: #333;min-height: 100vh;display: flex;flex-direction: column;}header {background-color: #35424a;color: white;padding: 1rem;}nav {background-color: #e8491d;padding: 0.7rem;}nav ul {list-style: none;display: flex;gap: 1.5rem;}nav a {color: white;text-decoration: none;font-weight: bold;}nav a:hover {color: #ffd700;}.container {flex: 1;padding: 2rem;max-width: 1200px;margin: 0 auto;width: 100%;}footer {background-color: #35424a;color: white;text-align: center;padding: 1rem;margin-top: auto;}.active {color: #ffd700 !important;}</style><!-- 页面特定CSS,可被子页面替换 --><th:block th:fragment="customCss"></th:block>
</head>
<body><!-- 页面头部 --><header th:fragment="header"><h1>My Spring Boot + Thymeleaf Site</h1></header><!-- 导航栏 --><nav th:fragment="navbar"><ul><li><a th:href="@{/}" th:classappend="${currentPage == 'home' ? 'active' : ''}">Home</a></li><li><a th:href="@{/about}" th:classappend="${currentPage == 'about' ? 'active' : ''}">About</a></li><li><a th:href="@{/services}" th:classappend="${currentPage == 'services' ? 'active' : ''}">Services</a></li><li><a th:href="@{/contact}" th:classappend="${currentPage == 'contact' ? 'active' : ''}">Contact</a></li><li><a th:href="@{/register}" th:classappend="${currentPage == 'register' ? 'active' : ''}">Register</a></li></ul></nav><!-- 主要内容区域,将被子页面内容替换 --><main class="container" th:fragment="content"><!-- 内容将由子页面填充 --></main><!-- 页脚 --><footer th:fragment="footer"><p>&copy; 2025 My Site. All rights reserved.</p><p th:text="${#dates.year(#dates.createNow())}">2025</p></footer><!-- 公共JavaScript --><script th:fragment="commonJs">// 公共JS功能console.log('Common JavaScript loaded');// 简单的页面加载动画document.addEventListener('DOMContentLoaded', function() {document.body.classList.add('loaded');});</script><!-- 页面特定JavaScript,可被子页面替换 --><th:block th:fragment="customJs"></th:block>
</body>
</html>

步骤2:创建可复用的组件片段

components.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<body><!-- 通知消息组件 --><div th:fragment="notification(message, type)" class="notification" th:classappend="${type}"><p th:text="${message}">Notification message</p><button type="button" class="close" onclick="this.parentElement.style.display='none'">&times;</button><style>.notification {padding: 15px;margin: 10px 0;border-radius: 4px;color: white;position: relative;}.notification.success {background-color: #4CAF50;}.notification.error {background-color: #f44336;}.notification.info {background-color: #2196F3;}.notification.warning {background-color: #ff9800;}.close {position: absolute;right: 10px;top: 5px;color: white;font-weight: bold;cursor: pointer;border: none;background: none;font-size: 1.2em;}</style></div><!-- 卡片组件 --><div th:fragment="card(title, content)" class="card"><div class="card-header"><h3 th:text="${title}">Card Title</h3></div><div class="card-body" th:utext="${content}">Card content</div><style>.card {border: 1px solid #ddd;border-radius: 4px;margin: 10px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);}.card-header {padding: 10px 15px;background-color: #f8f9fa;border-bottom: 1px solid #ddd;}.card-body {padding: 15px;}</style></div><!-- 按钮组件 --><button th:fragment="button(label, type, href)" th:type="${type == 'link' ? '_self' : 'button'}"th:href="${type == 'link' ? href : null}"th:classappend="'btn btn-' + ${type}"th:text="${label}">Button</button><style th:fragment="buttonStyles">.btn {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;font-size: 14px;text-decoration: none;display: inline-block;text-align: center;}.btn-primary {background-color: #2196F3;color: white;}.btn-success {background-color: #4CAF50;color: white;}.btn-danger {background-color: #f44336;color: white;}.btn-link {background: none;color: #2196F3;text-decoration: underline;}</style>
</body>
</html>

步骤3:创建使用布局的子页面

首页(home.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<!-- 继承主布局 -->
<head th:replace="layouts/main-layout :: ~{head}"><title>Home</title>
</head>
<body><!-- 替换头部 --><header th:replace="layouts/main-layout :: header"></header><!-- 替换导航栏 --><nav th:replace="layouts/main-layout :: navbar"></nav><!-- 替换主内容区 --><main th:replace="layouts/main-layout :: content"><div class="container"><h2>Welcome to Our Home Page</h2><!-- 引入通知组件 --><div th:replace="fragments/components :: notification('Welcome to our site!', 'info')"></div><p>This is an lihaozhe of a home page using Thymeleaf layout fragments.</p><div class="card-container" style="display: flex; flex-wrap: wrap;"><!-- 引入卡片组件 --><div th:replace="fragments/components :: card('Spring Boot', 'Powerful framework for building Java applications with minimal configuration.')"></div><div th:replace="fragments/components :: card('Thymeleaf', 'Modern server-side Java template engine for web and standalone environments.')"></div><div th:replace="fragments/components :: card('Web Development', 'Create powerful web applications with Spring Boot and Thymeleaf.')"></div></div><div style="margin-top: 20px;"><!-- 引入按钮组件和按钮样式 --><style th:replace="fragments/components :: buttonStyles"></style><div th:replace="fragments/components :: button('Learn More', 'primary', '@{/about}')"></div><div th:replace="fragments/components :: button('Contact Us', 'success', '@{/contact}')"></div></div></div></main><!-- 替换页脚 --><footer th:replace="layouts/main-layout :: footer"></footer><!-- 引入公共JS --><script th:replace="layouts/main-layout :: commonJs"></script><!-- 页面特定JS --><script th:replace="layouts/main-layout :: customJs">// 首页特定JavaScriptconsole.log('Home page JavaScript loaded');</script>
</body>
</html>

关于页面(about.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="layouts/main-layout :: ~{head}"><title>About</title><!-- 页面特定CSS --><th:block th:replace="layouts/main-layout :: customCss"><style>.team-member {display: flex;align-items: center;margin: 20px 0;}.team-member img {border-radius: 50%;margin-right: 20px;width: 100px;height: 100px;}</style></th:block>
</head>
<body><header th:replace="layouts/main-layout :: header"></header><nav th:replace="layouts/main-layout :: navbar"></nav><main th:replace="layouts/main-layout :: content"><div class="container"><h2>About Our Company</h2><p>We are a team of dedicated professionals specializing in Spring Boot and Thymeleaf development.</p><h3>Our Team</h3><div class="team-member"><img src="https://picsum.photos/id/1005/100/100" alt="Team member portrait"><div><h4>John Doe</h4><p>Lead Developer with over 10 years of experience in Java development.</p></div></div><div class="team-member"><img src="https://picsum.photos/id/1011/100/100" alt="Team member portrait"><div><h4>Jane Smith</h4><p>UI/UX Specialist focusing on creating beautiful and functional web interfaces.</p></div></div><div th:replace="fragments/components :: notification('We are hiring!', 'success')"></div><div style="margin-top: 20px;"><style th:replace="fragments/components :: buttonStyles"></style><div th:replace="fragments/components :: button('Join Our Team', 'primary', '@{/register}')"></div></div></div></main><footer th:replace="layouts/main-layout :: footer"></footer><script th:replace="layouts/main-layout :: commonJs"></script><script th:replace="layouts/main-layout :: customJs">// 关于页面特定JavaScriptconsole.log('About page JavaScript loaded');</script>
</body>
</html>

步骤4:创建处理布局页面的控制器

package com.lihaozhe.demo.controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;/*** 布局页面控制器* 处理使用主布局的各个页面请求*/
@Controller
public class LayoutController {/*** 处理首页请求* @param model 模型对象* @return 首页视图名*/@GetMapping("/")public String home(Model model) {// 设置页面标题和当前页标识(用于导航栏激活状态)model.addAttribute("pageTitle", "Home");model.addAttribute("currentPage", "home");return "home";}/*** 处理关于页面请求* @param model 模型对象* @return 关于页面视图名*/@GetMapping("/about")public String about(Model model) {model.addAttribute("pageTitle", "About Us");model.addAttribute("currentPage", "about");return "about";}/*** 处理服务页面请求* @param model 模型对象* @return 服务页面视图名*/@GetMapping("/services")public String services(Model model) {model.addAttribute("pageTitle", "Our Services");model.addAttribute("currentPage", "services");return "services";}/*** 处理联系页面请求* @param model 模型对象* @return 联系页面视图名*/@GetMapping("/contact")public String contact(Model model) {model.addAttribute("pageTitle", "Contact Us");model.addAttribute("currentPage", "contact");return "contact";}
}

4.4 运行与测试

  1. 启动应用
  2. 访问以下页面,观察布局的一致性和导航栏的激活状态:
    • http://localhost:8080/(首页)
    • http://localhost:8080/about(关于页面)
    • 注意页面公共部分(头部、导航、底部)的一致性
    • 观察复用组件(通知、卡片、按钮)的展示效果

4.5 章节总结

本章我们学习了:

  • 如何创建和使用布局模板
  • 定义和复用页面片段
  • 实现页面继承和内容替换
  • 创建可复用的UI组件
  • 动态设置页面标题和导航状态

这些技术极大地提高了代码复用率,简化了页面维护,并确保了网站的一致性,是开发大型Web应用的必备技能。

第5章:国际化与本地化

5.1 理论基础

国际化(Internationalization,i18n)是指应用程序能够适应不同语言和地区的需求。本地化(Localization,l10n)是指为特定地区或语言调整应用程序的过程。

Spring Boot与Thymeleaf结合提供了完整的国际化支持:

  • 消息源(MessageSource):管理不同语言的消息
  • 区域解析器(LocaleResolver):确定用户的语言/地区偏好
  • Thymeleaf消息表达式:#{...}用于显示国际化消息

核心组件:

  • MessageSource:加载消息资源文件
  • LocaleResolver:解析用户区域设置
  • LocaleChangeInterceptor:允许通过请求参数切换区域

5.2 开发思路

  1. 配置Spring Boot国际化支持
  2. 创建不同语言的消息资源文件
  3. 开发区域解析器和拦截器
  4. 在Thymeleaf模板中使用国际化消息
  5. 实现语言切换功能

5.3 实践示例

步骤1:添加国际化配置类

package com.lihaozhe.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;import java.util.Locale;/*** 国际化配置类* 配置区域解析器和语言切换拦截器*/
@Configuration
public class InternationalizationConfig implements WebMvcConfigurer {/*** 配置区域解析器* 使用SessionLocaleResolver存储用户的区域设置到会话中* @return LocaleResolver实例*/@Beanpublic LocaleResolver localeResolver() {SessionLocaleResolver resolver = new SessionLocaleResolver();// 设置默认区域为英文resolver.setDefaultLocale(Locale.ENGLISH);return resolver;}/*** 配置区域切换拦截器* 允许通过请求参数"lang"切换语言* @return LocaleChangeInterceptor实例*/@Beanpublic LocaleChangeInterceptor localeChangeInterceptor() {LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();// 设置触发语言切换的请求参数名interceptor.setParamName("lang");return interceptor;}/*** 注册拦截器* @param registry 拦截器注册表*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(localeChangeInterceptor());}
}

步骤2:创建消息资源文件

默认英文消息(messages.properties):

# 通用消息
site.title=My Spring Boot Site
welcome.message=Welcome to our site!
home.page=Home
about.page=About
services.page=Services
contact.page=Contact
register.page=Register
copyright.message=All rights reserved# 首页消息
home.welcome=Welcome to the Home Page
home.description=This is an lihaozhe of internationalization with Spring Boot and Thymeleaf.
home.learn.more=Learn More
home.contact.us=Contact Us# 关于页面消息
about.title=About Our Company
about.description=We specialize in Spring Boot and Thymeleaf development.
about.team=Our Team
about.john=Lead Developer with over 10 years of experience in Java development.
about.jane=UI/UX Specialist focusing on creating beautiful and functional web interfaces.
about.hiring=We are hiring!
about.join.team=Join Our Team# 注册表单消息
register.title=User Registration
register.username=Username
register.email=Email
register.password=Password
register.fullname=Full Name
register.dob=Date of Birth
register.submit=Register
register.username.required=Username is required
register.username.size=Username must be between 3 and 20 characters
register.email.required=Email is required
register.email.invalid=Please provide a valid email address
register.password.required=Password is required
register.password.size=Password must be at least 6 characters
register.fullname.required=Full name is required
register.dob.required=Date of birth is required
register.dob.age=You must be at least 18 years old# 注册成功消息
success.title=Registration Successful
success.info=Your Information
success.username=Username
success.email=Email
success.fullname=Full Name
success.dob=Date of Birth
success.another=Register Another User

中文消息(messages_zh.properties):

# 通用消息
site.title=我的Spring Boot网站
welcome.message=欢迎访问我们的网站!
home.page=首页
about.page=关于我们
services.page=服务
contact.page=联系我们
register.page=注册
copyright.message=版权所有# 首页消息
home.welcome=欢迎来到首页
home.description=这是一个使用Spring Boot和Thymeleaf实现国际化的示例。
home.learn.more=了解更多
home.contact.us=联系我们# 关于页面消息
about.title=关于我们公司
about.description=我们专注于Spring Boot和Thymeleaf开发。
about.team=我们的团队
about.john=首席开发师,拥有超过10年的Java开发经验。
about.jane=UI/UX专家,专注于创建美观且功能强大的网页界面。
about.hiring=我们正在招聘!
about.join.team=加入我们的团队# 注册表单消息
register.title=用户注册
register.username=用户名
register.email=电子邮箱
register.password=密码
register.fullname=全名
register.dob=出生日期
register.submit=注册
register.username.required=用户名是必填项
register.username.size=用户名必须在3到20个字符之间
register.email.required=电子邮箱是必填项
register.email.invalid=请提供有效的电子邮箱地址
register.password.required=密码是必填项
register.password.size=密码至少需要6个字符
register.fullname.required=全名是必填项
register.dob.required=出生日期是必填项
register.dob.age=您必须年满18岁# 注册成功消息
success.title=注册成功
success.info=您的信息
success.username=用户名
success.email=电子邮箱
success.fullname=全名
success.dob=出生日期
success.another=注册另一个用户

步骤3:修改模板使用国际化消息

修改首页(home.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="layouts/main-layout :: ~{head}"><title th:text="#{home.page}">Home</title>
</head>
<body><header th:replace="layouts/main-layout :: header"></header><nav th:replace="layouts/main-layout :: navbar"></nav><main th:replace="layouts/main-layout :: content"><div class="container"><!-- 语言切换器 --><div style="text-align: right; margin-bottom: 20px;"><span>Language: </span><a th:href="@{/(lang=en)}">English</a> | <a th:href="@{/(lang=zh)}">中文</a></div><h2 th:text="#{home.welcome}">Welcome to Our Home Page</h2><!-- 引入通知组件 --><div th:replace="fragments/components :: notification(#{welcome.message}, 'info')"></div><p th:text="#{home.description}">This is an lihaozhe of a home page using Thymeleaf layout fragments.</p><div class="card-container" style="display: flex; flex-wrap: wrap;"><!-- 引入卡片组件 --><div th:replace="fragments/components :: card('Spring Boot', 'Powerful framework for building Java applications with minimal configuration.')"></div><div th:replace="fragments/components :: card('Thymeleaf', 'Modern server-side Java template engine for web and standalone environments.')"></div><div th:replace="fragments/components :: card('i18n', 'Internationalization support for global applications.')"></div></div><div style="margin-top: 20px;"><!-- 引入按钮组件和按钮样式 --><style th:replace="fragments/components :: buttonStyles"></style><div th:replace="fragments/components :: button(#{home.learn.more}, 'primary', '@{/about}')"></div><div th:replace="fragments/components :: button(#{home.contact.us}, 'success', '@{/contact}')"></div></div></div></main><footer th:replace="layouts/main-layout :: footer"></footer><script th:replace="layouts/main-layout :: commonJs"></script><script th:replace="layouts/main-layout :: customJs">// 首页特定JavaScriptconsole.log('Home page JavaScript loaded');</script>
</body>
</html>

修改注册表单(register.html):

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title th:text="#{register.title}">User Registration</title><style>.form-group {margin-bottom: 15px;}label {display: inline-block;width: 120px;}input[type="text"],input[type="email"],input[type="password"],input[type="date"] {width: 250px;padding: 5px;}.error {color: red;margin-left: 125px;font-size: 0.9em;}.error-message {color: red;margin: 10px 0;padding: 10px;border: 1px solid #ff0000;background-color: #ffe6e6;}button {margin-left: 125px;padding: 8px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}.language-switch {text-align: right;margin-bottom: 20px;}</style>
</head>
<body><h1 th:text="#{register.title}">User Registration</h1><!-- 语言切换器 --><div class="language-switch"><span>Language: </span><a th:href="@{/register(lang=en)}">English</a> | <a th:href="@{/register(lang=zh)}">中文</a></div><!-- 显示全局错误信息 --><div th:if="${#fields.hasGlobalErrors()}" class="error-message"><p th:each="error : ${#fields.globalErrors()}" th:text="#{${error}}">Error message</p></div><!-- 表单标签 --><form th:object="${user}" th:action="@{/register}" method="post"><!-- 用户名输入框 --><div class="form-group"><label for="username" th:text="#{register.username}">Username:</label><input type="text" th:field="*{username}" /><!-- 显示字段验证错误 --><p th:if="${#fields.hasErrors('username')}" th:errors="*{username}" class="error" th:text="#{${#fields.errors('username')[0]}}">Username error</p></div><!-- 邮箱输入框 --><div class="form-group"><label for="email" th:text="#{register.email}">Email:</label><input type="email" th:field="*{email}" /><p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="error" th:text="#{${#fields.errors('email')[0]}}">Email error</p></div><!-- 密码输入框 --><div class="form-group"><label for="password" th:text="#{register.password}">Password:</label><input type="password" th:field="*{password}" /><p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="error" th:text="#{${#fields.errors('password')[0]}}">Password error</p></div><!-- 全名输入框 --><div class="form-group"><label for="fullName" th:text="#{register.fullname}">Full Name:</label><input type="text" th:field="*{fullName}" /><p th:if="${#fields.hasErrors('fullName')}" th:errors="*{fullName}" class="error" th:text="#{${#fields.errors('fullName')[0]}}">Full name error</p></div><!-- 出生日期输入框 --><div class="form-group"><label for="dateOfBirth" th:text="#{register.dob}">Date of Birth:</label><input type="date" th:field="*{dateOfBirth}" /><p th:if="${#fields.hasErrors('dateOfBirth')}" th:errors="*{dateOfBirth}" class="error" th:text="#{${#fields.errors('dateOfBirth')[0]}}">Date of birth error</p></div><!-- 提交按钮 --><div class="form-group"><button type="submit" th:text="#{register.submit}">Register</button></div></form><p><a th:href="@{/}" th:text="#{home.page}">Back to Home</a></p>
</body>
</html>

步骤4:更新布局模板支持国际化

修改主布局(main-layout.html)中的导航部分:

<!-- 导航栏 -->
<nav th:fragment="navbar"><ul><li><a th:href="@{/}" th:text="#{home.page}" th:classappend="${currentPage == 'home' ? 'active' : ''}">Home</a></li><li><a th:href="@{/about}" th:text="#{about.page}" th:classappend="${currentPage == 'about' ? 'active' : ''}">About</a></li><li><a th:href="@{/services}" th:text="#{services.page}" th:classappend="${currentPage == 'services' ? 'active' : ''}">Services</a></li><li><a th:href="@{/contact}" th:text="#{contact.page}" th:classappend="${currentPage == 'contact' ? 'active' : ''}">Contact</a></li><li><a th:href="@{/register}" th:text="#{register.page}" th:classappend="${currentPage == 'register' ? 'active' : ''}">Register</a></li></ul>
</nav>

5.4 运行与测试

  1. 启动应用
  2. 访问首页 http://localhost:8080
  3. 使用页面上的语言切换器切换语言(English/中文)
  4. 验证所有页面元素是否正确翻译
  5. 测试注册表单,验证错误消息是否也被国际化

5.5 章节总结

本章我们学习了:

  • 国际化和本地化的基本概念
  • 如何配置Spring Boot的国际化支持
  • 创建和使用多语言消息资源文件
  • 在Thymeleaf模板中使用国际化消息表达式
  • 实现语言切换功能

国际化是开发面向全球用户的应用程序的关键技术,能够为不同语言背景的用户提供更友好的体验。

第6章:Thymeleaf高级特性与最佳实践

6.1 理论基础

Thymeleaf提供了许多高级特性,能够应对复杂的Web开发场景:

  • 模板缓存:提高应用性能
  • 条件性片段:根据条件包含不同片段
  • 模板逻辑:使用Thymeleaf进行复杂逻辑处理
  • 安全集成:与Spring Security无缝集成
  • 最佳实践:项目结构、性能优化、代码组织等

6.2 开发思路

  1. 配置Thymeleaf缓存和性能优化
  2. 实现高级片段逻辑和条件性渲染
  3. 集成Spring Security并在模板中使用安全表达式
  4. 展示Thymeleaf最佳实践和项目结构

6.3 实践示例

步骤1:配置Thymeleaf缓存和性能优化

更新应用配置(application.properties):

# 服务器端口
server.port=8080# Thymeleaf配置
spring.thymeleaf.encoding=UTF-8
# 开发环境关闭缓存,生产环境开启
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html# 模板解析器配置
spring.thymeleaf.template-resolver-order=1
# 模板缓存TTL(生存时间),生产环境可设置为较大值
spring.thymeleaf.cache-ttl=60s# 启用模板缓存统计
spring.thymeleaf.enable-cache-statistics=true# 国际化配置
spring.messages.basename=messages
spring.messages.encoding=UTF-8
# 消息缓存时间,-1表示永久缓存
spring.messages.cache-duration=-1

步骤2:创建高级片段和条件渲染示例

advanced-components.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<body><!-- 条件性导航菜单 --><div th:fragment="dynamicNav(userRole)"><nav><ul><li><a th:href="@{/}">Home</a></li><!-- 所有用户可见的菜单 --><li><a th:href="@{/public}">Public Area</a></li><!-- 仅登录用户可见的菜单 --><th:block th:if="${userRole != null}"><li><a th:href="@{/user}">User Area</a></li></th:block><!-- 仅管理员可见的菜单 --><th:block th:if="${userRole == 'ADMIN'}"><li><a th:href="@{/admin}">Admin Area</a></li><li><a th:href="@{/users}">Manage Users</a></li></th:block><!-- 根据登录状态显示登录/退出按钮 --><li th:if="${userRole == null}"><a th:href="@{/login}">Login</a></li><li th:if="${userRole != null}"><a th:href="@{/logout}">Logout</a></li></ul></nav><style>nav ul {list-style: none;display: flex;gap: 1rem;background-color: #f0f0f0;padding: 1rem;}nav a {text-decoration: none;color: #333;}nav a:hover {text-decoration: underline;}</style></div><!-- 带参数的分页组件 --><div th:fragment="pagination(currentPage, totalPages, baseUrl)"><div class="pagination"><!-- 上一页按钮 --><a th:href="${currentPage > 1 ? @{${baseUrl}(page=${currentPage - 1})} : '#'" th:classappend="${currentPage <= 1 ? 'disabled' : ''}">Previous</a><!-- 页码链接 --><th:block th:each="i : ${#numbers.sequence(1, totalPages)}"><a th:href="@{${baseUrl}(page=${i})}" th:text="${i}"th:classappend="${i == currentPage ? 'active' : ''}">1</a></th:block><!-- 下一页按钮 --><a th:href="${currentPage < totalPages ? @{${baseUrl}(page=${currentPage + 1})} : '#'"th:classappend="${currentPage >= totalPages ? 'disabled' : ''}">Next</a></div><style>.pagination {margin: 20px 0;}.pagination a {display: inline-block;padding: 8px 16px;text-decoration: none;border: 1px solid #ddd;margin: 0 4px;color: #333;}.pagination a.active {background-color: #4CAF50;color: white;border: 1px solid #4CAF50;}.pagination a.disabled {color: #cccccc;pointer-events: none;}.pagination a:hover:not(.active):not(.disabled) {background-color: #ddd;}</style></div><!-- 数据表格组件 --><div th:fragment="dataTable(headers, items)"><table><thead><tr><th th:each="header : ${headers}" th:text="${header}">Header</th></tr></thead><tbody><!-- 如果没有数据,显示空消息 --><tr th:if="${#lists.isEmpty(items)}"><td th:attr="colspan=${headers.size()}" style="text-align: center;">No data available</td></tr><!-- 遍历显示数据 --><tr th:each="item : ${items}"><td th:each="header, stat : ${headers}" th:text="${item[stat.index]}">Value</td></tr></tbody></table><style>table {width: 100%;border-collapse: collapse;}th, td {padding: 12px;text-align: left;border-bottom: 1px solid #ddd;}th {background-color: #f2f2f2;}tr:hover {background-color: #f5f5f5;}</style></div>
</body>
</html>

步骤3:创建使用高级组件的页面

demo.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf Advanced Features</title><style>.container {max-width: 1200px;margin: 0 auto;padding: 20px;}.section {margin-bottom: 40px;padding: 20px;border: 1px solid #ddd;border-radius: 4px;}h2 {color: #2c3e50;}</style>
</head>
<body><div class="container"><h1>Thymeleaf Advanced Features Demo</h1><!-- 动态导航部分 --><div class="section"><h2>Dynamic Navigation (based on user role)</h2><p>Current role: <strong th:text="${userRole} ?: 'Anonymous'">Anonymous</strong></p><!-- 引入动态导航组件,传递用户角色参数 --><div th:replace="fragments/advanced-components :: dynamicNav(${userRole})"></div></div><!-- 分页组件部分 --><div class="section"><h2>Pagination Component</h2><p>lihaozhe of a reusable pagination component:</p><!-- 引入分页组件,传递当前页、总页数和基础URL参数 --><div th:replace="fragments/advanced-components :: pagination(3, 10, '/products')"></div></div><!-- 数据表格部分 --><div class="section"><h2>Data Table Component</h2><p>Reusable data table with dynamic headers and data:</p><!-- 引入数据表格组件,传递表头和数据参数 --><div th:replace="fragments/advanced-components :: dataTable(${headers}, ${products})"></div></div><!-- 模板逻辑示例 --><div class="section"><h2>Template Logic lihaozhes</h2><!-- 复杂条件判断 --><div th:if="${userRole == 'ADMIN'}"><p>Welcome, Administrator! You have full access.</p></div><div th:elseif="${userRole == 'USER'}"><p>Welcome, User! You have limited access.</p></div><div th:else><p>Please log in to access more features.</p></div><!-- 字符串操作 --><div><p>Original: <span th:text="${sampleText}">Sample text</span></p><p>Uppercase: <span th:text="${#strings.toUpperCase(sampleText)}">UPPERCASE</span></p><p>Length: <span th:text="${#strings.length(sampleText)}">0</span></p><p>Contains 'Thymeleaf'? <span th:text="${#strings.contains(sampleText, 'Thymeleaf')}">false</span></p></div><!-- 日期操作 --><div><p>Current Date: <span th:text="${#dates.format(currentDate, 'yyyy-MM-dd HH:mm:ss')}">2023-01-01 12:00:00</span></p><p>Year: <span th:text="${#dates.year(currentDate)}">2023</span></p><p>Month: <span th:text="${#dates.monthName(currentDate)}">January</span></p><p>Days from now to Christmas: <span th:text="${#dates.daysBetween(currentDate, christmas)}">0</span></p></div></div></div><p><a th:href="@{/}">Back to Home</a></p>
</body>
</html>

步骤4:创建处理高级页面的控制器

package com.lihaozhe.demo.controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** 高级特性控制器* 展示Thymeleaf的高级特性和最佳实践*/
@Controller
public class AdvancedController {/*** 展示Thymeleaf高级特性页面* @param model 模型对象* @return 高级特性页面视图名*/@GetMapping("/advanced")public String showAdvancedFeatures(Model model) {// 设置用户角色(模拟,实际应用中从安全上下文获取)model.addAttribute("userRole", "ADMIN"); // 可改为"USER"或null测试不同角色// 示例文本model.addAttribute("sampleText", "Thymeleaf Advanced Features Demo");// 日期示例model.addAttribute("currentDate", LocalDate.now());model.addAttribute("christmas", LocalDate.of(LocalDate.now().getYear(), Month.DECEMBER, 25));// 表格数据 - 表头List<String> headers = Arrays.asList("ID", "Name", "Price", "In Stock");model.addAttribute("headers", headers);// 表格数据 - 产品列表List<List<Object>> products = new ArrayList<>();products.add(Arrays.asList(1, "Laptop", 999.99, true));products.add(Arrays.asList(2, "Smartphone", 699.99, true));products.add(Arrays.asList(3, "Tablet", 299.99, false));products.add(Arrays.asList(4, "Headphones", 149.99, true));products.add(Arrays.asList(5, "Smartwatch", 199.99, true));model.addAttribute("products", products);return "advanced/demo";}
}

步骤5:Spring Security与Thymeleaf集成示例

首先添加Spring Security依赖到pom.xml:

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

创建安全配置类:

<package com.lihaozhe.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;/*** Spring Security配置类* 配置安全规则和用户认证*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {/*** 配置安全过滤链* @param http HttpSecurity对象* @return SecurityFilterChain实例* @throws Exception 可能的异常*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((requests) -> requests// 允许所有人访问首页和公共资源.requestMatchers("/", "/home", "/about", "/public/**", "/register").permitAll()// 管理员页面需要ADMIN角色.requestMatchers("/admin/**", "/users").hasRole("ADMIN")// 其他受保护资源需要认证.anyRequest().authenticated())// 配置登录页面.formLogin((form) -> form.loginPage("/login").permitAll())// 配置退出登录.logout((logout) -> logout.permitAll().logoutSuccessUrl("/"));return http.build();}/*** 配置内存用户存储(实际应用中会替换为数据库存储)* @return UserDetailsService实例*/@Beanpublic UserDetailsService userDetailsService() {// 创建普通用户UserDetails user = User.builder().username("user").password(passwordEncoder().encode("password")).roles("USER").build();// 创建管理员用户UserDetails admin = User.builder().username("admin").password(passwordEncoder().encode("admin")).roles("ADMIN", "USER").build();return new InMemoryUserDetailsManager(user, admin);}/*** 配置密码编码器* @return PasswordEncoder实例*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

创建包含安全表达式的模板:

demo.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head><meta charset="UTF-8"><title>Spring Security + Thymeleaf</title><style>.container {max-width: 800px;margin: 0 auto;padding: 20px;}.section {margin: 20px 0;padding: 15px;border: 1px solid #ddd;border-radius: 4px;}.username {color: #2c3e50;font-weight: bold;}.role {color: #e74c3c;}</style>
</head>
<body><div class="container"><h1>Spring Security with Thymeleaf</h1><!-- 登录状态信息 --><div class="section"><h2>Authentication Status</h2><!-- 未登录状态 --><div sec:authorize="isAnonymous()"><p>You are not logged in.</p><p><a th:href="@{/login}">Login</a></p></div><!-- 已登录状态 --><div sec:authorize="isAuthenticated()"><p>You are logged in as: <span class="username" sec:authentication="name"></span></p><p>Your roles: <span class="role" sec:authentication="principal.authorities"></span></p><p><a th:href="@{/logout}">Logout</a></p></div></div><!-- 基于角色的内容展示 --><div class="section"><h2>Role-Based Access</h2><!-- 所有登录用户可见 --><div sec:authorize="isAuthenticated()"><p>This content is visible to all authenticated users.</p><p><a th:href="@{/user}">User Area</a></p></div><!-- 仅管理员可见 --><div sec:authorize="hasRole('ADMIN')"><p>This content is visible only to ADMIN users.</p><p><a th:href="@{/admin}">Admin Area</a></p><p><a th:href="@{/users}">Manage Users</a></p></div><!-- 非管理员可见 --><div sec:authorize="hasRole('USER') and !hasRole('ADMIN')"><p>This content is visible only to regular USERs.</p></div></div><!-- 安全URL示例 --><div class="section"><h2>Secure URLs</h2><ul><li><a th:href="@{/}">Public Home</a> - Accessible to everyone</li><li><a th:href="@{/user}">User Area</a> - Requires authentication</li><li><a th:href="@{/admin}">Admin Area</a> - Requires ADMIN role</li></ul></div></div>
</body>
</html>

6.4 运行与测试

  1. 启动应用

  2. 访问高级特性页面 http://localhost:8080/advanced

  3. 观察动态导航如何根据用户角色变化

  4. 查看分页组件和数据表格的展示效果

  5. 访问安全集成页面 http://localhost:8080/security

  6. 测试不同用户角色(未登录、普通用户、管理员)的访问权限

    • 普通用户:用户名user,密码password
    • 管理员:用户名admin,密码admin

6.5 章节总结

本章我们学习了:

  • Thymeleaf的缓存配置和性能优化
  • 高级片段和条件性渲染技术
  • 分页组件和数据表格的复用
  • Thymeleaf与Spring Security的集成
  • 安全表达式在模板中的应用

这些高级特性使Thymeleaf能够应对复杂的Web开发场景,同时保持代码的简洁和可维护性。

结语

本教程全面介绍了Spring Boot 3.5.6与Thymeleaf的整合开发,从基础环境搭建到高级特性应用,涵盖了实际项目开发中所需的主要知识点。

通过本教程,你应该能够:

  • 搭建完整的Spring Boot + Thymeleaf开发环境
  • 使用Thymeleaf模板语法展示动态数据
  • 处理表单提交和数据验证
  • 实现页面布局和组件复用
  • 开发支持多语言的国际化应用
  • 应用Thymeleaf高级特性和最佳实践

Thymeleaf作为一款强大的模板引擎,与Spring Boot的完美结合为Java Web开发提供了高效、优雅的解决方案。

随着你在实际项目中的应用,将会发现更多的技巧和最佳实践,不断提升你的开发效率和代码质量。

祝你在Spring Boot与Thymeleaf的开发之路上取得更多成就!

http://www.dtcms.com/a/515537.html

相关文章:

  • 自动化部署脚本
  • 上海专业网站建设方案wordpress 送女友
  • 人与狗做的电影网站网页搭建服务
  • 前端 数据的转换
  • React useCallback介绍(用来缓存函数的引用,避免每次渲染都重新创建函数)主要用于性能优化
  • VUE工程化开发模式
  • 海口澄迈县建设局网站杭州萧山网络
  • 前端低代码开发工具的崛起与实践经验分享,从效率到可控性的平衡
  • 二、redis集群部署(3主3从)
  • Vue 生命周期详解
  • vue3调用ant-design-vue组件库的a-table组件
  • 手机网站开发软件南昌网站外包
  • 以图搜图随州网站seo诊断
  • java设计模式六、装饰器模式
  • 微信小程序隐藏滚动条多种方法教程
  • AWS DMS实现MySQL到Redshift的CDC增量数据复制方案
  • 王者重名生成查询抖音快手微信小程序看广告流量主开源
  • 旅游网站建设1000字软文范例800字
  • 网站搜索引擎友好性最近三天发生的重要新闻
  • Flink的checkpoint interval与mini-batch什么区别?
  • CADSoftTools发布两款重要更新:CAD VCL Multiplatform 16.2 与 CAD .NET 16全新发布
  • 【个人成长笔记】在本地Windows系统中如何正确使用adb pull命令,把Linux系统中的文件或文件夹复制到本地中(亲测有效)
  • 触摸未来2025-10-22:语序之困
  • 【滑动窗口与双指针】【定长滑窗】—1456. 定长子串中元音的最大数目
  • Flink 实验性特性把“已预分区”的 DataStream 重新解释为 KeyedStream
  • ADB -> 常用文件操作的组合命令
  • 网格系统网站济南网约车公司
  • 社区网站的推广方案手机写wordpress博客
  • 原子性、可见性和指令重排问题的根源
  • 什么是测试覆盖率?如何衡量?