会话超时后登录触发ViewExpiredException,求保留h:body的解决办法
针对你遇到的这几个JSF应用的问题,结合你的技术栈,我整理了几个针对性的解决方案:
一、先解决会话超时设置不生效的问题
你把会话超时硬编码为5秒但实际超时更长,大概率是被其他配置或组件行为覆盖了,按这个顺序排查:
- 检查容器默认配置:比如Tomcat的
web.xml里如果有<session-config>设置了超时,会优先于代码设置。要么把这个配置的超时值改得比5秒大,要么直接删掉,让代码设置生效。 - 确认Spring Security会话配置:Spring Security的
<security:session-management>如果有相关设置,也可能影响会话超时。确保这里没有强制设置更长的超时时间,同时invalid-session-url指向正确的登录页。 - 修正代码设置方式:确保你在正确的时机设置会话超时,比如在WelcomeBean的
@PostConstruct方法里:
@PostConstruct public void initSessionTimeout() { HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true); session.setMaxInactiveInterval(5); // 单位是秒 }
- 排查PrimeFaces/Atmosphere的心跳请求:PrimeFaces的AJAX组件(比如
<p:poll>)或者Atmosphere的推送功能会定期发送请求,自动延长会话时间。检查页面里有没有这类组件,要么把它们的间隔设得大于5秒,要么关闭不必要的心跳逻辑。
二、处理会话超时后的ViewExpiredException
会话超时后登录触发这个异常,是因为JSF视图依赖的会话数据已经失效,你可以通过以下方式处理:
- 自定义JSF异常处理器:在
faces-config.xml里配置自定义异常处理工厂,捕获ViewExpiredException并重定向到登录页:
<application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> <exception-handler-factory>com.yourpackage.CustomExceptionHandlerFactory</exception-handler-factory> </application>
然后实现对应的处理器类:
public class CustomExceptionHandler extends ExceptionHandlerWrapper { private final ExceptionHandler wrapped; public CustomExceptionHandler(ExceptionHandler wrapped) { this.wrapped = wrapped; } @Override public ExceptionHandler getWrapped() { return wrapped; } @Override public void handle() throws FacesException { Iterator<ExceptionQueuedEvent> events = getUnhandledExceptionQueuedEvents().iterator(); while (events.hasNext()) { ExceptionQueuedEvent event = events.next(); ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource(); Throwable throwable = context.getException(); if (throwable instanceof ViewExpiredException) { FacesContext fc = FacesContext.getCurrentInstance(); ExternalContext ec = fc.getExternalContext(); try { // 重定向到登录页,带上超时标识 ec.redirect(ec.getRequestContextPath() + "/login.xhtml?sessionExpired=true"); fc.renderResponse(); } catch (IOException e) { throw new FacesException("重定向到登录页失败", e); } finally { events.remove(); } } } getWrapped().handle(); } } public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory { private final ExceptionHandlerFactory parent; public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) { this.parent = parent; } @Override public ExceptionHandler getExceptionHandler() { return new CustomExceptionHandler(parent.getExceptionHandler()); } }
- 结合Spring Security配置:在Spring Security的xml配置里补充会话超时的处理,确保超时后直接跳转到登录页:
<security:http auto-config="true" use-expressions="true"> <!-- 其他权限配置 --> <security:session-management invalid-session-url="/login.xhtml?sessionExpired"> <security:concurrency-control maximum-sessions="1" expired-url="/login.xhtml?sessionExpired"/> </security:session-management> </security:http>
三、解决h:body导致的异常问题
替换成普通<body>就正常,说明h:body的渲染逻辑和会话超时后的状态有冲突,试试这些方向:
- 检查h:body绑定的事件:如果
h:body上绑定了preRenderView之类的事件(比如<f:event type="preRenderView" listener="#{welcomeBean.someMethod}"/>),会话超时后这些事件触发时会因为会话失效抛出异常。把这类初始化逻辑移到托管bean的@PostConstruct方法里,而不是视图渲染事件。 - 排查PrimeFaces主题/组件冲突:PrimeFaces会给
h:body注入额外的JS和CSS资源,会话超时后这些资源加载可能出问题。可以临时在web.xml里关闭PrimeFaces主题测试:
<context-param> <param-name>primefaces.THEME</param-name> <param-value>none</param-value> </context-param>
如果问题消失,再排查是哪个主题或组件导致的冲突。
- 处理Atmosphere连接残留:因为你用了Atmosphere,会话超时后Atmosphere的推送连接可能还没关闭,导致
h:body渲染时出错。可以在自定义ExceptionHandler里添加关闭Atmosphere连接的逻辑:
// 在重定向前关闭Atmosphere连接 AtmosphereRequest atmosphereReq = (AtmosphereRequest) ec.getRequest(); if (atmosphereReq != null) { AtmosphereResource resource = atmosphereReq.getAttribute(AtmosphereResource.class.getName()); if (resource != null) { resource.close(); } }
- 检查PrettyFaces URL重写:PrettyFaces的重写规则可能和
h:body的渲染逻辑冲突,确保登录页的URL没有被重写,或者在pretty-config.xml里明确配置登录页的映射:
<url-mapping id="loginPage"> <pattern value="/login" /> <view-id value="/login.xhtml" /> </url-mapping>
内容的提问来源于stack exchange,提问作者Nachiket Doke
相关产品推荐
相关产品推荐

