迁移到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:
- 修改控制器注解:
import jakarta.faces.view.ViewScoped; import org.springframework.stereotype.Component; @Component @ViewScoped public class YourController extends AbstractController { // 原有逻辑保持不变 }
- 配置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
相关产品推荐
相关产品推荐

