When does JSF creates a session & what does it put

2019-03-01 18:07发布

问题:

I am running Mojarra 2.2.0.

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

The managed bean action method is-

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

For stateless views session.getId() throws NPE

For views which are not stateless- Firing a GET request, there is JSESSIONID=340041C96D5AA446D761C3602F54A76D

I read it here that-

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.

Further, it's mentioned here 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

I think the above line is a source for trouble for me.

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

and

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.

In reference to the discussion which I had with BalusC in this question, I created a HttpSessionListener-

@WebListener
public class MyHttpSessionListener implements HttpSessionListener {

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

    public void sessionDestroyed(HttpSessionEvent event) {

    }

}

See below attached screenshots(these 2 screenshots are for version 2.0.3, there must have been an old bug due to which the session was getting created)-

Libraby (Mojarra 2.2.0)-

回答1:

When does JSF creates a session

Eaiest way to naildown this is creating a HttpSessionListener, putting a debug breakpoint on sessionCreated() method and inspecting the call stack who needed to get the session for the first time (and thus implicitly needs to create it).

In below example you will see a chain of getSession() calls in the call stack. You will see that FaceletViewHandlingStrategy.renderView() method is the one calling it for the first time.

After you click on FaceletViewHandlingStrategy.renderView() line in debugger's call stack, you will get into its source code (Maven will load source code automatically, otherwise you need to manually attach it).

You see, when server side state saving is enabled and the view to render is not transient (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).

You'll in the source code also immediately see that when the state saving method is set to client, or when the view is stateless as in <f:view transient="true">, then JSF won't anymore implicitly create the session. Older JSF versions may do that as you figured, but this is to be accounted as a bug and ought to be fixed in a newer version.

If you would like to ensure statelessness and avoid accidental/unforeseen session creation, then you could just throw new IllegalStateException() inside sessionCreated() method. When that happens, you just have to look in call stack who's responsible for creating the session and then fix/change the code to not do that anymore.


what does it puts in a session map?

Under the covers, ExternalContext#getSessionMap() delegates to HttpSession#setAttribute()/getAttribute()/removeAttribute(). You can listen on those methods using a HttpSessionAttributeListener.

In below example you will see that ViewScopeContextManager.getContextMap() line calls SessionMap#put() method in order to put something in the session map. When you unfold the event argument, you will see the session attribute name and value, which is com.sun.faces.application.view.activeViewContexts and an empty ConcurrentHashMap respectively.

Indeed, I was using a @Named @ViewScoped which was referenced by an value expression on the particular page (you see EL resolver and Weld resolver further down in call stack). When you click ViewScopeContextManager.getContextMap() line in call stack, you'll see that it was just preparing a map in session scope in order to store view scoped beans.

That's just one example. There are more things which could be stored in the session. Using a debugger this way and inspecting the associated source code will tell a lot about the Why.