JSF 何时创建会话以及它在会话映射中放置什么?

When does JSF creates a session & what does it puts in a session map?

我是 运行 Mojarra 2.2.0。

  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>

托管bean动作方法是-

public void action() {
        HttpSession session = (HttpSession) FacesContext.getCurrentInstance()
                .getExternalContext().getSession(false);
        System.out.println(session.getId()); // not null for stateful views
    }

对于 stateless 次观看 session.getId() 抛出 NPE

对于非无状态的视图- 触发 GET 请求,有 JSESSIONID=340041C96D5AA446D761C3602F54A76D

我读了 here 那-

For client side state saving mechanism, JSF won't create the session and will store the view state in a hidden input field with the name javax.faces.ViewState in the form whenever necessary.

另外这里提到that

JSF will indeed autocreate the session because the JSF view state has to be stored over there. If you set the JSF state saving method to client instead of server, then it won't be stored in session and hence no session needs to be created

我认为上面这行是给我带来麻烦的根源。

If you set the JSF state saving method to client instead of server, then it won't be stored in session // FULLY AGREED

hence no session needs to be created. // This confuses because for client side saving mechanism, a session id gets generated by the servlet container & hence there is a session associated with the request.

关于我在这个 question 中与 BalusC 的讨论, 我创建了一个 HttpSessionListener-

@WebListener
public class MyHttpSessionListener implements HttpSessionListener {

    public void sessionCreated(HttpSessionEvent event) {
        Thread.dumpStack();
    }

    public void sessionDestroyed(HttpSessionEvent event) {

    }

}

见下面附上的截图(这 2 个截图是 2.0.3 版的,一定是由于创建会话的旧错误)-

Libraby (Mojarra 2.2.0)-

When does JSF creates a session

确定这一点的最简单方法是创建一个 HttpSessionListener,在 sessionCreated() 方法上放置一个调试断点,并检查第一次需要获取会话的调用堆栈(因此隐式需要创建它)。

在下面的示例中,您将在调用堆栈中看到一个 getSession() 调用链。您会看到 FaceletViewHandlingStrategy.renderView() 方法是第一次调用它的方法。

点击调试器调用堆栈中的FaceletViewHandlingStrategy.renderView()行后,您将进入其源代码(Maven会自动加载源代码,否则您需要手动附加它)。

你看,当启用服务器端状态保存并且要呈现的视图不是瞬态时 (stateless), then JSF will implicitly create the session, just to ensure it's created on time in order to save the view (if the session was created later, e.g. during render response phase, you would otherwise risk exceptions like this Adding <h:form> causes java.lang.IllegalStateException: Cannot create a session after the response has been committed)。

您还会在源代码中立即看到,当状态保存方法设置为客户端时,或者当视图像 <f:view transient="true"> 中那样是无状态的时,JSF 将不再隐式创建会话.较旧的 JSF 版本可能会像您想象的那样做到这一点,但这将被视为一个错误,应该在较新的版本中修复。

如果您想确保无状态并避免创建 accidental/unforeseen 会话,那么您可以在 sessionCreated() 方法中 throw new IllegalStateException() 。发生这种情况时,您只需查看负责创建会话的调用堆栈,然后 fix/change 不再执行此操作的代码。


what does it puts in a session map?

在幕后,ExternalContext#getSessionMap() delegates to HttpSession#setAttribute()/getAttribute()/removeAttribute(). You can listen on those methods using a HttpSessionAttributeListener

在下面的示例中,您将看到 ViewScopeContextManager.getContextMap() 行调用 SessionMap#put() 方法以便将某些内容放入会话映射中。当你展开event参数时,你会看到会话属性名称和值,分别是com.sun.faces.application.view.activeViewContexts和一个空的ConcurrentHashMap

事实上,我使用的是 @Named @ViewScoped,它被特定页面上的值表达式引用(您可以在调用堆栈的下方看到 EL 解析器和 Weld 解析器)。当您单击调用堆栈中的 ViewScopeContextManager.getContextMap() 行时,您会看到它只是在会话范围内准备一个映射以存储视图范围内的 bean。

这只是一个例子。会话中可以存储更多的东西。以这种方式使用调试器并检查相关的源代码将告诉我们很多关于 Why.

的信息