仅在FacesContext
服务于由调用FacesServlet
. 在会话销毁期间,不一定意味着 HTTP 请求。会话通常由容器管理的后台线程销毁。这不会通过FacesServlet
. 因此,您不应期望FacesContext
在会话销毁期间始终存在。仅当您session.invalidate()
在 JSF 托管 bean 内部调用时,FacesContext
它才可用。
如果您的应用程序范围的托管 bean 由 JSF 管理@ManagedBean
,那么很高兴知道 JSF 将其作为ServletContext
. 反过来ServletContext
在会话监听器中可用HttpSession#getServletContext()
。
所以,这应该这样做:
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
ApplicationScopedBean appBean = (ApplicationScopedBean) session.getServletContext().getAttribute("appBean");
appBean.getConnectedUsers().remove(user);
}
如果您正在运行支持 Servlet 3.0 的容器,则另一种方法是让您的应用程序范围的 bean 实现HttpSessionListener
并在构造时将其自身注册。这样您就可以直接引用该connectedUsers
属性。
@ManagedBean
@ApplicationScoped
public class AppBean implements HttpSessionListener {
public AppBean() {
ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
context.addListener(this);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
connectedUsers.remove(user);
}
// ...
}
另一种选择是将会话范围内的 bean 保留User
为会话范围的托管 bean。然后,您可以使用@PreDestroy
注释来标记应在会话被销毁时调用的方法。
@ManagedBean
@SessionScoped
public class User {
@ManagedProperty("#{appBean}")
private AppBean appBean;
@PreDestroy
public void destroy() {
appBean.getConnectedUsers().remove(this);
}
// ...
}
这具有额外的好处,即User
在 EL 上下文中可用作#{user}
.