Spring Boot+JSF/CDI中@ViewScoped Bean意外表现为会话/应用作用域
问题分析与解决方案
你遇到的核心问题是标注了@Named和@ViewScoped的Bean未按预期销毁,页面切换后输入值依然保留,本质是ViewScoped作用域未被正确识别和管理,以下是具体排查和解决步骤:
1. 修正@ViewScoped注解的导入路径
你使用的是CDI的@Named注解,必须搭配JSF 2.3+兼容CDI的@ViewScoped,不能用旧版JSF原生的javax.faces.bean.ViewScoped(这个注解仅适配JSF原生的@ManagedBean,和CDI体系不兼容)。
正确的导入路径:
- Jakarta EE 8+环境:
jakarta.faces.view.ViewScoped - Java EE 8环境:
javax.faces.view.ViewScoped
同时注意类名符合Java规范(首字母大写),并确保属性的Getter/Setter完整实现(EL表达式依赖这些方法读写值):
import jakarta.faces.view.ViewScoped; import jakarta.inject.Named; import java.io.Serializable; @Named("parameterBean") @ViewScoped public class ParameterBean implements Serializable { private String spreadValue; // 必须实现Getter和Setter public String getSpreadValue() { return spreadValue; } public void setSpreadValue(String spreadValue) { this.spreadValue = spreadValue; } }
2. 给Spring Boot添加ViewScoped支持
Spring Boot默认不原生支持JSF的ViewScoped,需要手动配置作用域管理器,让Spring容器能正确管理ViewScoped Bean的生命周期。在你的JSFConfig类中补充以下代码:
import org.springframework.beans.factory.config.CustomScopeConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import jakarta.faces.context.FacesContext; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletRegistration; import jakarta.servlet.annotation.WebServlet; import javax.faces.webapp.FacesServlet; import java.util.HashMap; import java.util.Map; @Configuration public class JSFConfig { @Bean ServletRegistrationBean<FacesServlet> jsfServletRegistration(ServletContext servletContext) { servletContext.setInitParameter("com.sun.faces.forceLoadConfiguration", Boolean.TRUE.toString()); ServletRegistrationBean<FacesServlet> srb = new ServletRegistrationBean<>(); srb.setServlet(new FacesServlet()); srb.setUrlMappings(Arrays.asList("*.xhtml")); srb.setLoadOnStartup(1); return srb; } // 添加ViewScope的Spring配置 @Bean public CustomScopeConfigurer viewScopeConfigurer() { CustomScopeConfigurer configurer = new CustomScopeConfigurer(); Map<String, Object> scopes = new HashMap<>(); scopes.put("view", new ViewScope()); configurer.setScopes(scopes); return configurer; } // 自定义ViewScope实现,绑定JSF视图上下文 static class ViewScope implements org.springframework.beans.factory.config.Scope { @Override public Object get(String name, org.springframework.beans.factory.ObjectFactory<?> objectFactory) { FacesContext facesContext = FacesContext.getCurrentInstance(); if (facesContext == null) { throw new IllegalStateException("FacesContext未初始化"); } Map<String, Object> viewMap = facesContext.getViewRoot().getViewMap(); if (!viewMap.containsKey(name)) { viewMap.put(name, objectFactory.getObject()); } return viewMap.get(name); } @Override public Object remove(String name) { return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name); } @Override public void registerDestructionCallback(String name, Runnable callback) {} @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; } } }
3. 确保页面导航使用重定向
如果页面切换用的是服务器转发(forward),会复用同一个视图上下文,ViewScoped Bean不会销毁。必须使用重定向来触发视图上下文的切换:
- 导航规则中添加
<redirect/>:
<!-- faces-config.xml --> <navigation-rule> <from-view-id>/page1.xhtml</from-view-id> <navigation-case> <from-outcome>toPage2</from-outcome> <to-view-id>/page2.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule>
- 或者在Bean的导航方法中返回带
?faces-redirect=true的结果:
public String switchPage() { return "/targetPage.xhtml?faces-redirect=true"; }
4. 优化冗余的AJAX配置
你的p:inputText同时配置了change和valueChange两个AJAX事件,PrimeFaces中这两个事件的行为几乎完全重复,保留一个即可,避免不必要的请求:
<p:inputText value="#{parameterBean.spreadValue}" label=" " styleClass="inputText" id="spreadValue"> <p:ajax event="change" /> </p:inputText>
内容的提问来源于stack exchange,提问作者SmokingTurtle
相关产品推荐
相关产品推荐

