下载工作台
Spring Boot Web 开发

Thymeleaf 模板与表单

试读上半部分 · 解锁后可读全文

第 4 章 · Thymeleaf 模板与表单

本章目标:集成 Thymeleaf 服务端模板;实现商品列表/详情 HTML 页面(对标 shop-demo 的 catalog/ 模板);处理 表单提交、校验错误展示与重定向;理解 CSRF 机制及 Spring Security 启用后的 Token 行为;能对照 Django Template + {% csrf_token %} 说明异同;在 shop-spring-demo 中完成运营后台最小页面骨架。

学时建议:5~6 小时(含 2 小时跟练)

前置:完成 spring-boot-web ch03java-devfrontend-web 或 HTML 基础;django-web ch02 表单章节更佳。


4.1 场景说明:HTML 与 JSON 并存

shop-spring-demo 需同时服务:

通道技术用户
JSON API@RestController(ch03)移动端、前后端分离 SPA
HTML 页面@Controller + Thymeleaf运营人员浏览器访问

shop-demo(Django)templates/catalog/product_list.html 渲染列表;本章在 Spring 侧实现等价页面,域名仍经虚构 https://api.example.com 或独立 https://admin.example.com 访问(教学占位)。

shop-spring-demo 双通道
  /api/v1/*     → JSON(RestController)
  /catalog/*    → HTML(Controller + Thymeleaf)

4.2 引入 Thymeleaf

4.2.1 依赖

pom.xml 增加:

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

Initializr 可勾选 Thymeleaf 一并生成。

4.2.2 与 Django 模板对照

特性Django TemplatesThymeleaf
语法{{ var }} {% tag %}th:text="${var}"
布局extends / blockth:replace / layout dialect
静态文件{% static %}th:src="@{/css/app.css}"
自然模板可在浏览器直接打开 html 预览
默认引擎DjangoSpring Boot 自动配置 Thymeleaf

4.3 配置与目录

spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    cache: false          # 开发关闭缓存
  web:
    resources:
      static-locations: classpath:/static/
resources/
├── templates/
│   ├── layout/
│   │   └── main.html
│   └── catalog/
│       ├── product_list.html
│       └── product_form.html
└── static/
    └── css/
        └── shop.css

4.4 布局模板 layout/main.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title th:text="${pageTitle} ?: 'Shop Spring Demo'">Shop Spring Demo</title>
    <link rel="stylesheet" th:href="@{/css/shop.css}"/>
</head>
<body>
<header>
    <h1><a th:href="@{/catalog}">商品目录</a></h1>
</header>
<main th:replace="${content}">内容区</main>
<footer><small>教学项目 shop-spring-demo · api.example.com</small></footer>
</body>
</html>

也可使用 Thymeleaf Layout Dialect;本章用片段简化。


4.5 列表页 Controller

web/CatalogPageController.java

package com.example.shop.web;

import com.example.shop.catalog.ProductCatalogService;
import com.example.shop.catalog.ProductDto;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/catalog")
public class CatalogPageController {

    private final ProductCatalogService catalogService;

    public CatalogPageController(ProductCatalogService catalogService) {
        this.catalogService = catalogService;
    }

    @GetMapping
    public String list(Model model) {
        List<ProductDto> products = catalogService.listFeatured(50);
        model.addAttribute("products", products);
        model.addAttribute("pageTitle", "商品列表");
        return "catalog/product_list";
    }

    @GetMapping("/{id}")
    public String detail(@PathVariable Long id, Model model) {
        ProductDto product = catalogService.listFeatured(100).stream()
                .filter(p -> p.id().equals(id))
                .findFirst()
                .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
                        org.springframework.http.HttpStatus.NOT_FOUND));
        model.addAttribute("product", product);
        model.addAttribute("pageTitle", product.name());
        return "catalog/product_detail";
    }
}
返回值含义
"catalog/product_list"视图名 → templates/catalog/product_list.html
Model等同 Django context 字典

Django:

return render(request, "catalog/product_list.html", {"products": products})

4.6 列表模板 catalog/product_list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title th:text="${pageTitle}">商品列表</title>
    <link rel="stylesheet" th:href="@{/css/shop.css}"/>
</head>
<body>
<h1>商品列表</h1>
<p><a th:href="@{/catalog/new}">新建商品</a>(ch07 完善)</p>
<table>
    <thead>
    <tr><th>SKU</th><th>名称</th><th>价格</th><th>操作</th></tr>
    </thead>
    <tbody>
    <tr th:each="p : ${products}">
        <td th:text="${p.sku}">SKU</td>
        <td th:text="${p.name}">名称</td>
        <td th:text="${#numbers.formatDecimal(p.price, 1, 2)}">0.00</td>
        <td><a th:href="@{/catalog/{id}(id=${p.id})}">详情</a></td>
    </tr>
    <tr th:if="${#lists.isEmpty(products)}">
        <td colspan="4">暂无商品</td>
    </tr>
    </tbody>
</table>
</body>
</html>

4.6.1 常用 Thymeleaf 属性

属性作用
th:text转义文本输出
th:each循环
th:if / th:unless条件
th:href="@{/path}"URL 生成(含 context-path)
th:object / th:field表单绑定(见下节)

4.7 表单与命令对象

4.7.1 ProductForm(命令对象)

package com.example.shop.web.form;

import jakarta.validation.constraints.*;
import java.math.BigDecimal;

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。