I wanted to share a solution that I came up with using Spring 3 with session level Model Attributes and multiple tabs within the same session.
First here is a brief description of the problem: Basically the issue is that if you open the first tab and do a get to load the command object into the session and then open a second tab and do a get on the same controller with a different command object then the first tab's command object has been replaced by the second tab's command object and if you submit the form on the first tab then it will update the second tab's command object with the data from the first command object!
Spring by default uses the class DefaultSessionAttributeStore to store and retrieve the session level command objects based upon the session attribute name only. This is what causes the command objects to be stomped on by multiple requests within the same session with the same attribute name.
This is a known issue and a JIRA (SPR-4160) ticket has been created that will address this in version 3.1M1.
To address this issue until the fix is incorporated into Spring, I created the following solution:
I created the a class that implements the SessionAttributeStore interface. This class stores and retrieves the session level command objects based upon a unique (conversation id) that is automatically generated when the command object is stored on the session. So when you do a get request with a controller that has session attributes and you assign your command object to the model map, the storeAttribute method will be invoked and it will generate a unique conversation id (System.currTimeInMillis()) and append this to the attribute name (your command object name) and store it on the session as well as set the conversation id as a request attribute so that the view that is displayed can post back the conversation that it will be involved in.
The only other thing that needs to be done is add a hidden input field in your form that will post back the conversation id. The controller will invoke the retrieveAttribute method and based upon the incoming conversation id will retrieve the correct command object from the session. I created a simple tag library that will create a hidden input field for you.
It is pretty simple to setup, basically you do the following:
In your dispatcher-servlet.xml file you define the class that you want to do the storing and retrieving of command objects from the session:
Then you tell the AnnotationMethodHandlerAdapter class that you want to use a custom sessionAttributeStore:Code:<bean id="sessionConversationAttributeStore" class="com.marty.support.SessionConversationAttributeStore"> <property name="numConversationsToKeep" value="10"/> </bean>
Finally you add the following tag library call inside your forms that use session model attributes:Code:<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="webBindingInitializer"> <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService" /> </bean> </property> <property name="sessionAttributeStore"> <ref bean="sessionConversationAttributeStore"/> </property> </bean>
Here is a link to a sample application that shows how the custom SessionAttributeStore works along with the source code for the SessionConversationAttributeStore class and the SessionConversationIdTag tag library.Code:<sessionConversation:insertSessionConversationId attributeName="<your command name here>"/>
Hopefully this will help someone else with the issues of using session level model attributes like it has for me.
Marty Jones


Reply With Quote