Wednesday, October 21, 2015

Spring session scoped bean and HTTP session attribute

A simple JSF application app has a jsf file a.faces and a jsp file b.jsp. The jsp file has the following:
<%
    HttpSession sess = rquest.getSession();
    FooBean foo = (FooBean)sess.getAttribute("fooBean");
    foo.someMethod();
%>
And the jsf file has the following:
<ice:form onsubmit="#{fooBean.doAction}">
Here the tag ice is used because it is using icefaces. The name fooBean is for a Spring bean defined in applicationContext.xml as below:
<bean id="fooBean" class="FooBean" scope="session">
   <constructor-arg><ref bean="x" /> <constructor-arg>
   <constructor-arg><ref bean="y" /> <constructor-arg>
</bean>

<bean id="x" class="X"/>
<bean id="y" class="Y" scope="session"/>
And in the faces-config.xml, there is the following:
<faces-config>
 <application>
  <variable-resolver>
   org.springframework.web.jsf.DelegatingVariableResolver
  </variable-resolver>
 </application>
</faces-config>

The application is accessed by app/a.faces. And then you can access the jsp file app/b.jsp without any problem. But if you access the application using app/b.jsp as the first step, it will give NullPointerException when calling foo.someMethod() because foo is null.

The tricky thing here is how the "fooBean" is put into the session as an attribute. The answer is in the Spring DelegatingVariableResolver class and DefaultListableBeanFactory class.

When user types app/a.faces, the resolver sees #{fooBean.doAction}. It checks the applicationContext.xml and finds the bean definition. So it creates the bean x, y, and then fooBean. The bean x is a singleton. It is already created at the application startup. So the resolver only needs to get the cached instance of x. But the bean y has a scope of session. So the resolver calls DefaultListableBeanFactory and creates a brand new instance of y. And it puts it as an attribute into the HTTP Session object. After both x and y are created, a new fooBean is created and put into the session as an attribute since its scope is also session. So these session beans are created lazily when there are referenced.

Now if you access app/b.jsp without firstly accessing app/a.faces, the Spring resolver is not invoked. The session object has no attribute named "fooBean". And you will get a null object for this attribute.

The following is the method stacks showing the Spring bean is resolved and set into the HTTP session as an attribute.

owns: InterceptingServletSession  (id=15648) 
 MyAttributeListener.attributeAdded(HttpSessionBindingEvent) line: 13 
 EventsManager.notifySessionAttributeChange(HttpSession, String, Object, Object) line: 302 
 MemorySessionContext(SessionContext).notifySessionAttributeChange(HttpSession, String, Object, Object) line: 1479 
 MemorySessionData(SessionData).setAttribute(String, Object, boolean) line: 1121 
 MemorySessionData(SessionData).setAttribute(String, Object) line: 959 
 InterceptingServletSession(ProxyHttpSession).setAttribute(String, Object) line: 107 
 ServletSessionAttributeMap.setAttribute(String, Object) line: 20 
 ServletSessionAttributeMap(AbstractAttributeMap).put(Object, Object) line: 157 
 FacesRequestAttributes.setAttribute(String, Object, int) line: 108 
 SessionScope(AbstractRequestAttributesScope).get(String, ObjectFactory) line: 44 
 SessionScope.get(String, ObjectFactory) line: 90 
 DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class, Object[], boolean) line: 298 
 DefaultListableBeanFactory(AbstractBeanFactory).getBean(String, Class, Object[]) line: 185 
 DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 164 
 XmlWebApplicationContext(AbstractApplicationContext).getBean(String) line: 881 
 DelegatingVariableResolver.resolveSpringBean(FacesContext, String) line: 136 
 DelegatingVariableResolver.resolveVariable(FacesContext, String) line: 109 
 NamedValue.evaluate(ExpressionInfo) line: 145 
 ComplexValue.evaluate(ExpressionInfo) line: 166 
 ExpressionEvaluatorImpl.evaluate(ExpressionInfo) line: 263 
 ValueBindingImpl.getValue(FacesContext, String) line: 160 
 ValueBindingImpl.getValue(FacesContext) line: 143 
 HtmlForm(HtmlForm).getOnsubmit() line: 501 

No comments:

Post a Comment