1. 简介

本文将深入探讨如何在 Spring MVC 中结合 Thymeleaf 模板引擎,灵活地传递和渲染数据。

我们会以构建一个邮件模板为例,逐步演示如何将 Spring 后端数据注入到前端页面中,并通过 Thymeleaf 进行展示。内容涵盖 Model 属性、请求参数、Session、ServletContext 以及 Spring Bean 等多种数据来源的使用方式和注意事项。

2. 项目初始化

首先,我们需要引入必要的依赖项,确保项目支持 Web 和 Thymeleaf。

✅ 添加 Thymeleaf 启动器:

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

✅ 同时引入 Web 模块,用于构建 RESTful 接口:

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

这两个依赖是开发 Spring Boot + Thymeleaf 应用的基础组合。

接下来,我们将 Thymeleaf 模板文件存放在 src/main/resources/templates/mvcdata/ 目录下。每个示例对应一个独立的 HTML 模板:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <!-- 数据填充区域 -->
</html>

最后,创建核心控制器 EmailController,用于处理页面跳转和数据准备:

@Controller
public class EmailController {
    private ServletContext servletContext;

    public EmailController(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
}

⚠️ 注意:虽然并非所有场景都需要 ServletContext,但为了演示特定功能,我们在此显式注入它。

3. 使用 Model 属性传递数据

Model 是控制器向视图传递数据最常用的方式之一。适用于需要在请求处理过程中动态组装数据并渲染页面的场景。

方式一:通过方法参数注入 Model

@GetMapping("/email/modelattributes")
public String emailModel(Model model) {
    model.addAttribute("emailData", emailData);
    return "mvcdata/email-model-attributes";
}

Spring MVC 会在请求到达时自动注入 Model 实例,开发者只需添加属性即可。

在 Thymeleaf 模板中通过 ${} 表达式访问:

<p th:text="${emailData.emailSubject}">邮件主题</p>

方式二:使用 @ModelAttribute 注解预加载

你也可以在控制器中声明一个带 @ModelAttribute 的方法,Spring 会自动将其结果放入 Model 中:

@ModelAttribute("emailModelAttribute")
EmailData emailModelAttribute() {
    return emailData;
}

模板中可直接使用该属性名:

<p th:each="emailAddress : ${emailModelAttribute.getEmailAddresses()}">
    <span th:text="${emailAddress}"></span>
</p>

✅ 优势:适合跨多个接口共享公共数据(如用户信息、站点配置)
❌ 缺点:过度使用可能导致 Model 膨胀,影响性能

更多关于 ModelModelMapModelAndView 的区别,可参考相关进阶文档。

4. 通过请求参数(Request Parameters)传值

当数据来自 URL 查询参数时,可以使用 @RequestParam 注解接收,并在模板中通过 param 对象访问。

基本用法

@GetMapping("/email/requestparameters")
public String emailRequestParameters(
    @RequestParam(value = "emailsubject") String emailSubject) {
    return "mvcdata/email-request-parameters";
}

模板中通过 param 关键字获取参数值:

<p th:text="${param.emailsubject}"></p>

多值参数处理

支持同名参数多次出现(例如多选收件人):

@GetMapping("/email/requestparameters")
public String emailRequestParameters(
    @RequestParam("emailsubject") String emailSubject,
    @RequestParam("emailaddress") String emailAddress1,
    @RequestParam("emailaddress") String emailAddress2) {
    return "mvcdata/email-request-parameters";
}

Thymeleaf 提供两种方式读取:

✅ 方式一:使用 th:each 遍历所有值

<p th:each="emailaddress : ${param.emailaddress}">
    <span th:text="${emailaddress}"></span>
</p>

✅ 方式二:通过索引访问数组元素

<p th:text="${param.emailaddress[0]}"></p>
<p th:text="${param.emailaddress[1]}"></p>

⚠️ 注意:param 返回的是字符串数组,即使只有一个值也需注意类型。

5. Session 属性共享数据

若需跨请求保持状态(如登录用户信息),可将数据存入 HttpSession

@GetMapping("/email/sessionattributes")
public String emailSessionAttributes(HttpSession session) {
    session.setAttribute("emaildata", emailData);
    return "mvcdata/email-session-attributes";
}

在 Thymeleaf 中通过 session 对象访问:

<p th:text="${session.emaildata.emailSubject}"></p>

⚠️ 重要提示:自 Thymeleaf 3.1 起,直接访问 session 已被标记为过时,出于安全考虑(防止敏感信息泄露)。

✅ 推荐做法:在控制器层将必要的 session 数据提取后放入 Model,仅暴露所需字段。

例如:

@GetMapping("/email/session-safe")
public String safeAccess(HttpSession session, Model model) {
    EmailData data = (EmailData) session.getAttribute("emaildata");
    model.addAttribute("subject", data.getEmailSubject());
    return "mvcdata/email-safe";
}

这样更安全、更可控。

6. ServletContext 属性(全局共享)

ServletContext 适合存放应用级别的全局数据(如系统配置、版本号),其生命周期与应用一致。

由于 Thymeleaf 不支持直接访问 ServletContext 中的对象属性,我们需要手动设置每个字段:

@GetMapping("/email/servletcontext")
public String emailServletContext() {
    servletContext.setAttribute("emailsubject", emailData.getEmailSubject());
    servletContext.setAttribute("emailcontent", emailData.getEmailBody());
    servletContext.setAttribute("emailaddress", emailData.getEmailAddress1());
    servletContext.setAttribute("emaillocale", emailData.getEmailLocale());
    return "mvcdata/email-servlet-context";
}

在模板中通过工具方法获取:

<p th:text="${#servletContext.getAttribute('emailsubject')}"></p>

⚠️ 同样,自 Thymeleaf 3.1 开始,#servletContext 已被弃用。

✅ 替代方案:使用 @Bean@ConfigurationProperties 将全局配置注入 Spring 容器,再通过 bean 方式访问(见下节)。

7. 通过 Spring Bean 注入数据

将数据对象注册为 Spring 容器中的 Bean,是实现全局可访问数据的优雅方式。

@Bean
public EmailData emailData() {
    return new EmailData();
}

Thymeleaf 支持通过 @beanName 语法直接调用 Bean 的 getter 方法:

<p th:text="${@emailData.emailSubject}"></p>

✅ 优点:

  • 全局可访问
  • 支持 DI,便于测试
  • 可结合 @ConfigurationProperties 实现配置绑定

❌ 注意事项:

  • Bean 必须是 public
  • 方法需有公开的 getter
  • 不建议在模板中执行复杂逻辑,保持视图纯净

8. 总结

本文系统梳理了 Spring MVC 与 Thymeleaf 集成时的五种主要数据传递方式:

方式 适用场景 是否推荐 备注
✅ Model 属性 请求级数据传递 ✔️ 强烈推荐 最常用、最安全
✅ Request 参数 URL 参数渲染 ✔️ 推荐 注意 XSS 防护
⚠️ Session 属性 用户会话数据 △ 限制使用 Thymeleaf 3.1+ 已弃用直接访问
⚠️ ServletContext 应用级全局数据 △ 替代为 Bean 已不推荐
✅ Bean 注入 全局配置/服务调用 ✔️ 推荐 结合 Spring 容器最佳实践

📌 最佳实践建议

  • 优先使用 Model + 控制器显式传值
  • 避免在模板中直接访问 sessionservletContext
  • 全局常量或配置应通过 @Bean@ConfigurationProperties 暴露
  • 所有输出注意防 XSS,Thymeleaf 默认已做 HTML 转义,无需额外处理

示例代码已托管至 GitHub:https://github.com/example/spring-thymeleaf-demo

踩坑提醒:升级 Thymeleaf 到 3.1+ 时务必检查模板中是否还在使用 session.#servletContext,否则运行时报错或安全警告。简单粗暴的修复方式就是改用 Model 显式传参。


原始标题:Spring MVC Data and Thymeleaf