1. 概述

Thymeleaf 是 Spring Boot 中默认集成的主流模板引擎之一,广泛用于服务端渲染动态 HTML 页面。我们之前也写过不少 Thymeleaf 相关的文章,感兴趣的同学可以查阅 Baeldung 的 Thymeleaf 系列教程

本文聚焦一个非常实用但容易踩坑的场景:如何在 Thymeleaf 中正确使用 selectoption 标签,实现下拉列表的动态渲染与选中控制。

对于有经验的开发者来说,这看似简单,但一旦涉及表单回显(如编辑场景),很容易掉进 th:field 和手动选中逻辑的坑里。

2. HTML 基础知识回顾

在 HTML 中,下拉列表由 <select> 和多个 <option> 组成:

<select>
    <option value="apple">Apple</option>
    <option value="banana">Banana</option>
    <option value="orange">Orange</option>
    <option value="pear">Pear</option>
</select>

关键点如下:

  • ✅ 浏览器默认会自动选中第一个 <option>
  • ✅ 使用 selected 属性可指定默认选中项:
    <option value="orange" selected>Orange</option>
    
  • ✅ 使用 disabled 可设置不可选的提示项(常用于“请选择…”):
    <option disabled>Please select...</option>
    

这些原生行为是理解 Thymeleaf 渲染逻辑的基础。

3. Thymeleaf 中的 select 与 option

Thymeleaf 提供了强大的数据绑定能力,最常用的是 th:field,它可以将表单元素与后端模型字段自动绑定。

<select th:field="*{gender}">
    <option th:value="'M'" th:text="Male"></option>
    <option th:value="'F'" th:text="Female"></option>
</select>

上面这段代码在“新增”场景下工作良好。但在“编辑”回显时,问题就来了 —— 特别是当你试图手动控制某个 option 的选中状态时。

3.1 不需要预选中的场景(Create)

假设我们要渲染一个 0~100 的百分比下拉框,在新增页面中无需默认选中任何值:

<select th:field="*{percentage}">
    <option th:each="i : ${#numbers.sequence(0, 100)}" th:value="${i}" th:text="${i}">
    </option>
</select>

Thymeleaf 会将其渲染为:

<select id="percentage" name="percentage">
    <option value="0">0</option>
    <option value="1">1</option>
    <option value="2">2</option>
    ...
    <option value="100">100</option>
</select>

✅ 这里 th:field="*{percentage}" 能正常工作,因为没有显式设置 selected,浏览器自然选中第一项(0),符合预期。

3.2 需要预选中的场景(Update)—— 踩坑高发区 ⚠️

现在我们扩展功能,支持“编辑”已有记录。比如之前保存的 percentage = 75,回显时希望自动选中 75。

你可能会这样写:

<select th:field="*{percentage}">
    <option th:each="i : ${#numbers.sequence(0, 100)}" 
            th:value="${i}" 
            th:text="${i}" 
            th:selected="${i == 75}">
    </option>
</select>

❌ 但结果是:**th:selected 完全失效!** 页面渲染后没有任何 selected="selected"

最终输出还是:

<select id="percentage" name="percentage">
    <option value="0">0</option>
    ...
    <option value="75">75</option> <!-- 没有 selected -->
    ...
</select>

❓ 为什么 th:selected 不生效?

根本原因在于:一旦使用了 th:field,Thymeleaf 就会完全接管 select 的选中逻辑,它只根据 *{percentage} 的值来决定哪个 option 被选中,而忽略你手动写的 th:selected

换句话说,th:fieldth:selected 是互斥的。你不能一边让 Thymeleaf 自动绑定,一边又想手动控制选中状态。

✅ 正确解法:放弃 th:field,改用 name + id

如果你需要手动控制选中项(比如从数据库查出的值不在标准序列中,或需要特殊逻辑判断),就必须放弃 th:field

<select id="percentage" name="percentage">
    <option th:each="i : ${#numbers.sequence(0, 100)}" 
            th:value="${i}" 
            th:text="${i}" 
            th:selected="${i == 75}">
    </option>
</select>

此时 Thymeleaf 不再接管选中逻辑,th:selected 生效,最终输出:

<select id="percentage" name="percentage">
    <option value="0">0</option>
    ...
    <option value="75" selected="selected">75</option>
    ...
</select>

✅ 成功!

💡 小结:

  • th:field 适合标准 CRUD 中的自动绑定场景(值在选项中存在)
  • 若需手动控制选中(如动态条件、默认提示项等),必须移除 th:field,改用 name 显式声明表单字段

4. 使用 List 动态填充下拉框

更常见的场景是从后端传入一个 List<String> 或对象列表,动态生成下拉选项。

后端 Controller 示例

@RequestMapping(value = "/populateDropDownList", method = RequestMethod.GET) 
public String populateList(Model model) {
    List<String> options = new ArrayList<>();
    options.add("option 1");
    options.add("option 2");
    options.add("option 3");
    model.addAttribute("options", options);
    return "dropDownList/dropDownList.html";
}

前端模板渲染

<select class="form-control" id="dropDownList" name="selectedOption">
    <option value="0">select option</option>
    <option th:each="option : ${options}" 
            th:value="${option}" 
            th:text="${option}">
    </option>
</select>

几点说明:

  • ✅ 使用 th:each 遍历 options 列表
  • ✅ 第一项是提示语,value="0" 表示未选择
  • ✅ 如果需要回显,且字段名为 selectedOption,可使用 th:field="*{selectedOption}" 自动匹配(前提是值在列表中)

⚠️ 注意:如果提示项的 value="0" 不在后端 options 列表中,而用户未选择直接提交,可能会导致绑定失败或异常。建议前端配合 JS 校验,或后端做默认值处理。

5. 总结

本文通过实际案例,梳理了 Thymeleaf 中 selectoption 的使用要点:

核心结论:

场景 是否使用 th:field 关键点
新增表单,无需预选 ✅ 可用 简单直接
编辑回显,值在选项中 ✅ 可用 Thymeleaf 自动匹配选中
编辑回显,需手动控制选中 ❌ 禁用 必须用 name + th:selected
动态列表渲染 ✅/❌ 视情况 结合 th:each 和数据源

⚠️ 最大坑点:不要试图在使用 th:field 的同时用 th:selected 控制选中,这是无效的。

所有示例代码已上传至 GitHub:https://github.com/example/thymeleaf-select-demo(mock 地址,实际项目请替换)。

掌握这些细节,能让你在开发表单页面时少走弯路,避免反复调试“为什么没选中”这类低级问题。


原始标题:Working with Select and Option in Thymeleaf