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

迁移到Spring Boot 3后preRenderView()在不同线程被调用两次

问题分析

迁移到Spring Boot 3 + Jakarta EE后,preRenderView()被不同线程调用两次,核心原因是:

  • Spring @Scope("view") 与 Jakarta EE JSF 视图作用域的兼容性变化,导致视图Bean被重复触发初始化
  • 自定义的flash.messages依赖SessionMap的remove()操作,第一次调用时已移除消息,第二次调用无法获取;同时两次调用分属不同线程,各自的FacesContext相互隔离,第一次添加的消息无法被最终页面渲染使用
解决方案

方案1:改用JSF原生Flash作用域管理消息

放弃手动操作SessionMap存储消息,JSF原生Flash作用域会自动处理跨请求消息的生命周期,避免重复调用导致的丢失:

存储消息时:

// 替换原有的sessionMap.put("flash.messages", messages)
FacesContext.getCurrentInstance().getExternalContext().getFlash().put("flashMessages", messages);
// 若需重定向后保留消息,可开启keepMessages
FacesContext.getCurrentInstance().getExternalContext().getFlash().setKeepMessages(true);

读取消息(修改showMessages方法):

public void showMessages() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    if (facesContext.getResponseComplete()) {
        return;
    }

    final var messages = Optional
        .ofNullable((List<FacesMessage>) facesContext.getExternalContext()
            .getFlash()
            .get("flashMessages"))
        .orElse(Collections.emptyList());

    for (final FacesMessage message : messages) {
        facesContext.addMessage(null, message);
    }
}

方案2:替换Spring视图作用域为JSF原生作用域

Spring @Scope("view") 在Jakarta EE环境下可能与JSF视图生命周期冲突,改用JSF原生@ViewScoped:

  1. 修改控制器注解:
import jakarta.faces.view.ViewScoped;
import org.springframework.stereotype.Component;

@Component
@ViewScoped
public class YourController extends AbstractController {
    // 原有逻辑保持不变
}
  1. 配置Spring支持JSF作用域(若未自动配置):
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.faces.webflow.JsfViewScope;

@Configuration
public class JsfConfig {
    @Bean
    public static JsfViewScope jsfViewScope() {
        return new JsfViewScope();
    }
}

方案3:线程级标记避免重复处理

若必须保留原有SessionMap存储方式,可通过ThreadLocal标记当前线程是否已处理消息,避免重复执行移除操作:

private static final ThreadLocal<Boolean> HAS_PROCESSED_MESSAGES = new ThreadLocal<>();

public void showMessages() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    if (facesContext.getResponseComplete() || Boolean.TRUE.equals(HAS_PROCESSED_MESSAGES.get())) {
        return;
    }

    try {
        HAS_PROCESSED_MESSAGES.set(true);
        final var messages = Optional
            .ofNullable((List<FacesMessage>) facesContext.getExternalContext()
                .getSessionMap()
                .remove("flash.messages"))
            .orElse(Collections.emptyList());

        for (final FacesMessage message : messages) {
            facesContext.addMessage(null, message);
        }
    } finally {
        HAS_PROCESSED_MESSAGES.remove();
    }
}
验证建议
  • 检查是否存在重复注册preRenderView事件的情况(比如同时在页面和控制器代码中注册)
  • 关闭Spring DevTools(若启用),热重载机制可能导致视图重复初始化

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:40:18