You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Reactive项目中如何触发Thymeleaf模板中非响应式值更新?

问题分析与解决方案

为什么isLoading标志不更新

你当前的代码中,modelMap.addAttribute("isLoading", isLoading.get())是将**AtomicBoolean的当前值(true)**存入模型,后续isLoading.set(false)的修改不会同步到模型中——因为模型里存储的是一个固定的布尔值,而非AtomicBoolean的引用。同时,Thymeleaf的服务器端渲染是一次性生成HTML内容(即使使用ReactiveDataDriverContextVariable流式输出,也是服务器端逐步推送HTML片段,而非实时更新已渲染的DOM),所以模板无法感知后端变量的后续变化。

是否可以混合响应式值与非响应式值

完全可以。Thymeleaf对Spring WebFlux的响应式类型(Mono/Flux)有原生支持,你可以在同一模板中同时使用响应式数据(如通过ReactiveDataDriverContextVariable处理的Flux)和普通的非响应式数据(如字符串、基本类型等),只要遵循响应式数据的渲染规则即可。

解决方案:实现加载器的显示与隐藏

推荐使用客户端JavaScript的方式处理,因为流式渲染的特性天然适合这种场景:浏览器会先接收到包含加载器的初始HTML,随后逐步接收Flux推送的表格行数据,此时通过JS监听DOM变化即可控制加载器的显示。

步骤1:修改模板,默认显示加载器

移除模板中th:if="${isLoading}"的判断,让加载器默认显示,然后通过JS在数据加载完成后隐藏:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <title>Spring Reactive</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" th:href="@{/main.css}"/>
    <link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.2.3/css/bootstrap.css}"/>
</head>
<body>
<main>
    <div class="card">
        <h5 class="card-header">Average age of a person according to his/her name</h5>
        <div class="card-body card__center">
            <table class="table table-striped">
                <thead>
                <tr>
                    <th>Name</th>
                    <th>Count</th>
                    <th>Age</th>
                </tr>
                </thead>
                <tbody>
                <tr data-th-each="age: ${ages}">
                    <td th:text="${age.name}"></td>
                    <td th:text="${age.count}"></td>
                    <td th:text="|${age.age} years|"></td>
                </tr>
                </tbody>
            </table>
            <!-- 默认显示加载器 -->
            <div class="spinner-border" role="status"></div>
        </div>
    </div>
</main>
<script>
// 监听表格tbody的子元素变化,一旦有数据行加载就隐藏加载器
const tbody = document.querySelector('tbody');
const spinner = document.querySelector('.spinner-border');

const observer = new MutationObserver(mutations => {
    if (tbody.children.length > 0) {
        spinner.style.display = 'none';
        observer.disconnect(); // 停止监听
    }
});

observer.observe(tbody, { childList: true });

// 兜底:页面完全加载后隐藏加载器
window.addEventListener('load', () => {
    spinner.style.display = 'none';
});
</script>
</body>
</html>

步骤2:简化后端代码

移除后端中关于AtomicBoolean的逻辑,因为加载状态不再由后端控制:

public Mono<ServerResponse> renderPersonsAge(ServerRequest ignoredServerRequest) {
    var ages = this.fetchPersonsAge();
    var contextVariable = new ReactiveDataDriverContextVariable(ages, 1);
    return ServerResponse.ok()
            .contentType(MediaType.TEXT_HTML)
            .render("index", Map.of("ages", contextVariable));
}

备选服务器端方案(不推荐)

如果你希望通过后端响应式变量控制加载状态,可以将isLoading包装为Mono<Boolean>,利用delayUntil等待Flux完成后切换状态:

public Mono<ServerResponse> renderPersonsAge(ServerRequest ignoredServerRequest) {
    var ages = this.fetchPersonsAge();
    // 初始为true,当ages完成后变为false
    Mono<Boolean> isLoading = Mono.just(true)
            .delayUntil(ignored -> ages.then());
    
    var contextVariable = new ReactiveDataDriverContextVariable(ages, 1);
    return ServerResponse.ok()
            .contentType(MediaType.TEXT_HTML)
            .render("index", Map.of(
                    "ages", contextVariable,
                    "isLoading", isLoading
            ));
}

模板中需要确保Thymeleaf能处理响应式布尔值:

<div th:if="${isLoading}" class="spinner-border" role="status"></div>

不过这种方式的体验不如客户端方案,因为服务器端需要等待Flux完成后才会推送isLoading=false的状态,加载器会一直显示到所有数据加载完成,无法实现逐行加载时提前隐藏。

内容的提问来源于stack exchange,提问作者Gilles

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:39:59