Thymeleaf 表达式

一、Thymeleaf 表达式概览

Thymeleaf 表达式以 ${...}*{...}#{...}@{...}~{...} 等形式存在,它们可以出现在 th:* 属性值中,也可以在内联脚本或文本中使用。每种表达式都有特定用途:

表达式类型 语法 主要用途
变量表达式 ${...} 获取后端传递的变量值,支持 OGNL/SpringEL 表达式
选择表达式 *{...} th:object 指定的对象上执行表达式,用于对象属性导航
消息表达式 #{...} 访问国际化消息资源文件(如 messages.properties)
链接表达式 `@{...}`` | 生成 URL,自动处理上下文路径和参数 |
片段表达式 ~{...} 引用模板片段,用于页面布局复用
预处理表达式 __${...}__ 在表达式执行前进行预处理,用于动态选择表达式

二、表达式详解与案例

1. 变量表达式 ${...}

这是最常用的表达式,它获取 Controller 放入 Model 的属性值,并可以执行属性访问、方法调用、算术运算等操作。

基本用法

复制代码
<p th:text="${user.name}">姓名</p>

支持的操作

  • 属性访问${user.name}${user['name']}

  • 方法调用${user.getFullName()}${list.size()}

  • 集合访问${list[0]}${map['key']}

  • 算术运算${totalPrice * 0.8}

  • 比较运算${age >= 18}

  • 逻辑运算${isAdmin and isActive}

  • 三元运算符${sex == 'M' ? '先生' : '女士'}

  • Elvis 操作符${user.name ?: '匿名'}(如果 user.name 为 null,显示"匿名")

2. 选择表达式 *{...}

选择表达式需要配合 th:object 使用,它直接在绑定的对象上执行表达式,简化代码。

复制代码
<form th:object="${user}">
    <input type="text" th:field="*{username}" />
    <input type="password" th:field="*{password}" />
</form>

*{username} 等价于 ${user.username}。选择表达式内部可以嵌套变量表达式:*{user.createdAt > ${yesterday}}

3. 消息表达式 #{...}

用于国际化,根据当前语言环境从资源文件中获取消息。

资源文件messages.properties):

复制代码
home.welcome=欢迎来到我的网站
user.greeting=你好,{0}!

模板中使用

复制代码
<h1 th:text="#{home.welcome}">默认欢迎语</h1>
<p th:text="#{user.greeting(${user.name})}">你好,用户!</p>

参数占位符 {0} 会被传入的值替换。

4. 链接表达式 @{...}

生成 URL,自动包含应用的上下文路径(context path)。可以附加查询参数或路径变量。

基本用法

复制代码
<a th:href="@{/user/profile}">个人资料</a>
<!-- 生成:/context/user/profile -->

<a th:href="@{/user/details(id=${user.id}, action='view')}">详情</a>
<!-- 生成:/context/user/details?id=123&action=view -->

<a th:href="@{/user/{id}/edit(id=${user.id})}">编辑</a>
<!-- 生成:/context/user/123/edit -->

<img th:src="@{/images/logo.png}" alt="Logo">

链接表达式还可以指定协议(如 http://),或引用相对路径。

5. 片段表达式 ~{...}

用于引用模板片段,通常与 th:insertth:replace 配合。

定义片段fragments/common.html):

复制代码
<div th:fragment="footer">
    <p>&copy; 2023 公司名称</p>
</div>

引用片段

复制代码
<div th:replace="~{fragments/common :: footer}"></div>

6. 预处理表达式 __${...}__

在表达式执行前先解析内部表达式,常用于动态决定变量名。

复制代码
<!-- 假设有一个变量 type = 'admin' -->
<div th:text="${__${type}.name__}"></div>
<!-- 预处理后变成 ${admin.name},然后执行 -->

7. 字面量与操作符

Thymeleaf 支持多种字面量:

  • 文本 :用单引号括起来,如 '这是一个字符串'

  • 数字1003.14

  • 布尔值truefalse

  • nullnull

操作符

  • 算术:+-*/%

  • 比较:><>=<===!=(也可以用 gtlt 等别名)

  • 逻辑:andornot!

  • 条件:(if) ? (then) : (else)?:(Elvis,若左侧为 null 则返回右侧)

示例

复制代码
<span th:text="${age} > 18 ? '成年' : '未成年'"></span>
<span th:text="${user.name} ?: '游客'"></span>

8. 内置对象与工具类

Thymeleaf 提供一系列以 # 开头的内置对象,方便处理常见任务。

内置对象 用途 示例
#dates 日期格式化与处理 ${#dates.format(date, 'yyyy-MM-dd')}
#calendars 日历对象操作 ${#calendars.createNow()}
#numbers 数字格式化 ${#numbers.formatDecimal(price, 1, 2)}
#strings 字符串操作 ${#strings.toUpperCase(name)}${#strings.isEmpty(text)}
#objects 对象工具 ${#objects.nullSafe(obj, '默认')}
#arrays 数组工具 ${#arrays.length(array)}
#lists 列表工具 ${#lists.size(list)}${#lists.isEmpty(list)}
#sets 集合工具 类似 lists
#maps Map 工具 ${#maps.size(map)}${#maps.containsKey(map, 'key')}
#aggregates 聚合函数 ${#aggregates.sum(numbers)}${#aggregates.avg(numbers)}
#messages 消息工具 ${#messages.msg('key')}
#uris URI 编码/解码 ${#uris.escapePath(uri)}
#conversions 类型转换 ${#conversions.convert(obj, 'java.lang.String')}
#ctx 上下文对象 ${#ctx.variables} 获取所有模型变量
#locale 当前语言环境 ${#locale.language}

示例

复制代码
<p th:text="${#dates.format(user.birthday, 'yyyy年MM月dd日')}">生日</p>
<p th:text="${#strings.capitalizeWords(user.fullName)}">姓名</p>
<p th:text="${#lists.size(products)}">产品数量</p>

三、综合案例分析:构建一个用户信息页面

下面通过一个完整的案例展示多种表达式的组合使用。假设我们有一个 Spring Boot 应用,包含用户信息展示、产品列表和片段布局。

1. 实体类

复制代码
public class User {
    private String name;
    private String email;
    private Date birthday;
    private boolean vip;
    // getter/setter 省略
}

public class Product {
    private String name;
    private BigDecimal price;
    private int stock;
    // getter/setter
}

2. Controller

复制代码
@Controller
public class HomeController {

    @GetMapping("/profile")
    public String profile(Model model) {
        User user = new User();
        user.setName("张三");
        user.setEmail("zhangsan@example.com");
        user.setBirthday(new Date());
        user.setVip(true);
        model.addAttribute("user", user);

        List<Product> products = Arrays.asList(
            new Product("笔记本电脑", new BigDecimal("5999.00"), 10),
            new Product("鼠标", new BigDecimal("99.00"), 50),
            new Product("键盘", new BigDecimal("299.00"), 0)
        );
        model.addAttribute("products", products);
        return "profile";
    }
}

3. 片段文件 fragments/common.html

复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <!-- 头部片段,带标题参数 -->
    <header th:fragment="header(title)">
        <h1 th:text="${title}">默认标题</h1>
        <nav>
            <a th:href="@{/}">首页</a> |
            <a th:href="@{/profile}">个人资料</a> |
            <a th:href="@{/products}">产品</a>
        </nav>
    </header>

    <!-- 页脚片段 -->
    <footer th:fragment="footer">
        <p th:text="#{footer.copyright}">&copy; 2023</p>
    </footer>
</body>
</html>

4. 国际化消息 messages.properties

复制代码
footer.copyright=© 2023 我的公司。保留所有权利。
user.vip=VIP 会员
user.regular=普通会员
product.stock.available=有货
product.stock.out=缺货

5. 主页面 profile.html

复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户资料</title>
    <style>
        .vip { color: gold; }
        .stock { font-weight: bold; }
        .out { color: red; }
    </style>
</head>
<body>
    <!-- 插入头部,传递标题参数 -->
    <div th:replace="~{fragments/common :: header('个人资料')}"></div>

    <main>
        <h2>用户信息</h2>
        <!-- 使用 th:object 绑定 user 对象 -->
        <div th:object="${user}">
            <p>姓名:<span th:text="*{name}">张三</span></p>
            <p>邮箱:<span th:text="*{email}">email@example.com</span></p>
            <p>生日:<span th:text="${#dates.format(*{birthday}, 'yyyy-MM-dd')}">1990-01-01</span></p>
            <p>
                会员类型:
                <!-- 条件判断 + 消息表达式 -->
                <span th:classappend="*{vip} ? 'vip' : ''"
                      th:text="*{vip} ? #{user.vip} : #{user.regular}">普通会员</span>
            </p>
        </div>

        <h2>产品列表</h2>
        <table border="1">
            <thead>
                <tr>
                    <th>产品名</th>
                    <th>价格</th>
                    <th>库存</th>
                    <th>状态</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="product : ${products}" th:object="${product}">
                    <td th:text="*{name}">产品</td>
                    <td th:text="${#numbers.formatDecimal(*{price}, 1, 2)} + '元'">0.00元</td>
                    <td th:text="*{stock}">0</td>
                    <td>
                        <span th:if="*{stock > 0}" class="stock" th:text="#{product.stock.available}">有货</span>
                        <span th:unless="*{stock > 0}" class="stock out" th:text="#{product.stock.out}">缺货</span>
                    </td>
                </tr>
            </tbody>
        </table>

        <!-- 链接表达式示例 -->
        <p>
            <a th:href="@{/profile/edit}">编辑资料</a> |
            <a th:href="@{/products/category(id=1, page=${currentPage})}">查看分类产品</a>
        </p>
    </main>

    <!-- 插入页脚 -->
    <div th:replace="~{fragments/common :: footer}"></div>
</body>
</html>

6. 页面渲染效果

访问 /profile 后:

  • 头部显示"个人资料",导航链接正确生成。

  • 用户信息区域展示姓名、邮箱、格式化后的生日,并根据 vip 值显示对应消息(VIP会员/普通会员),VIP 会员名称为金色。

  • 产品列表循环展示,库存状态通过 th:if 判断,并显示国际化文本"有货"/"缺货",缺货时显示红色字体。

  • 底部显示版权消息,通过 #{footer.copyright} 从国际化文件获取。

  • 链接表达式生成了带上下文路径的 URL,并附加了参数。

相关推荐
无尽的沉默1 小时前
Spring Boot 整合 Thymeleaf 模板引擎
java·前端·spring boot
Java后端的Ai之路1 小时前
【JDK】-JDK 11 新特性内容整理(很全面)
java·开发语言·后端·jdk
We་ct1 小时前
从输入URL到页面显示的完整技术流程
前端·edge·edge浏览器
czxyvX1 小时前
010-C++11
开发语言·c++
莫寒清2 小时前
Java 中 == 与 equals() 的区别
java·面试
冬夜戏雪2 小时前
腐烂橘子/课程表 相关
java
番茄去哪了2 小时前
苍穹外卖day07---Redis缓存优化与购物车功能实现
java·数据库·ide·spring boot·spring·maven·mybatis
先做个垃圾出来………2 小时前
DeepDiff差异语义化特性
服务器·前端
毕设源码-钟学长2 小时前
【开题答辩全过程】以 国产汽车的在线销售系统为例,包含答辩的问题和答案
java