Sunday, January 9, 2011

Spring MVC - Session Attributes handling

Spring Framework 2.5 has introduced annotation based programming model for MVC Controllers. Spring Framework 3 has made next step, and deprecated widely used SimpleFormController. Now we are all doomed to use annotations ;) but do we really understand what is going on behind the scenes?

Let's take a look at the @SessionAttributes and SessionStatus today :) If you are not familiar with them, you should start with Specifying attributes to store in a session with @SessionAttributes and Supported handler method arguments and return types before you will continue reading this post.

@SessionAttributes annotation is used on the Controller class level to declare used session attributes. All Model attributes having names or types defined in this annotation will be automatically persisted and restored between the subsequent requests. Sounds easy and beautiful, isn't it? Small declaration instead of boring HttpSession's getAttribute / setAttribute calls. Tempting :), try it and you'll find it can be dangerous too ...

"Automatically persisted" should alarm you :) - where? how? when? and for how long? - you should ask immediately :) - Well, let's take a deeper look at it :)

Strategy used for persisting Model attributes is defined by SessionAttributeStore implementation, Spring Framework by default uses DefaultSessionAttributeStore which in turn relies on HttpSession as the storage (for Servlet environment). You can provide custom implementation of the SessionAttributeStore interface, which you then enable by supplying a custom bean configuration for an AnnotationMethodHandlerAdapter (see Spring MVC pitfall - overriding default beans).

Model attributes are persisted at the end of request handling by AnnotationMethodHandlerAdapter, after calling your handler method responsible for request handling and after determining the ModelAndView for the response. Their names in Model are used as their names in the persistent storage, ex. @SessionAttributes("form") will trigger persisting Model attribute named "form" as HttpSession attribute named "form".

Persisted Model attributes will be removed only if you'll call SessionStatus.setComplete() method in some of your handler methods.

If at this point you still don't have any doubts, let me share my own with you:
  1. Cleaning not needed session attributes is a little tricky, because in a little more complicated applications users usually have possibilities to use Breadcrumb Navigation, some Menus, etc., each of them can cause switching the flow from one to another without calling SessionStatus.setComplete() method (and thus without cleaning the attributes used by the flow). 
  2. Using default SessionAttributeStore implementation and same names for Model attributes for different types of data will sooner or later lead to beautiful ClassCastException. Why? Because when some part of your application will persist attribute of class A under name "form", and user will then switch without cleaning it to other functionality, using also "form" attribute, but expecting it to be of class B, that will not end with the exception only if A extends B.
We all struggle with the HttpSession attributes cleaning, even not using Spring Framework, in fact. Both the problems mentioned by me above can be found outside the Spring Framework based application too. Using this beautiful and shining @SessionAttributes annotation you have to know how does it work, otherwise it will make you mad, instead of helping you.

For the dessert :) - few pitfalls you may encounter while using @SessionAttributes:
  1. When you refer to non-existing session attribute using @ModelAttribute annotation, HttpSessionRequiredException will be raised, like in following example:
    @SessionAttributes("abc")
    public class MyController {
    
        @RequestMapping(method = RequestMethod.GET)
        public void onGet(@ModelAttribute("abc") String something) {
            ...
        }
    }
    
    This exception, like the any other, can be then handled by you, using @ExceptionHandler annotation for example.
  2. SessionStatus.setComplete() method will trigger cleaning of Session Attributes, but only those which Spring will find "actual session attribute". Suppose that you declare 3 session attributes, but use only 1 of them in your handler method parameters, like in following example:
    @SessionAttributes({ "abc", "def", "ghi" })
    public class BindingTestController {
    
        @ModelAttribute("abc")
        public String createABC() {
            return "abc";
        }
    
        @RequestMapping(method = RequestMethod.GET)
        public void onGet(@ModelAttribute("abc") String something) {
            // do nothing :)
        }
    
        @RequestMapping(method = RequestMethod.POST)
        public void onPost(@ModelAttribute("abc") String something, BindingResult bindingResult, SessionStatus sessionStatus) {
            sessionStatus.setComplete();
        }
    
    }
    
    Only the "abc" attribute will be considered as "actual session attribute", and removed on POST request.
  3. Attribute will be flagged as "actual session attribute" while resolving @ModelAttribute annotated method parameters and AFTER request processing - in this case all Model attributes are checked if they should be persisted (according to @SessionAttributes annotation). 
  4. Assuming that you pass the parameter to some flow, using @SessionAttributes mechanism may lead to surprises, sometimes unpleasant :( Suppose that you want to start new flow programmatically, from Controller A, you put the flow parameter on session, under name "parameter", and mark the Controller B (handling the entry point to the flow) with the @SessionAttribute("parameter"). That will not work my Friend, "parameter" will not be restored into B Controller's Model on entry, because it is not an "actual session attribute" (see above), at least before the first request handling. (Spring 3.1 brings remedy for this problem, named Flash Attributes - see my post Spring MVC - Flash Attributes)

8 comments:

  1. At first I used Spring's @SessionAttributes to store model objects in the session when the user was creating/editing something. The biggest problem came when the user opened multiple editing forms in different browser tabs because session attributes were getting overriden and only the last tab was actually working.
    As I found no easy way to overcome this (and other) limitations, I decided to use form objects and not to store anything in the session between requests. This approach has some small problems needing more work but I found it to be robust enough.

    Regards!

    ReplyDelete
  2. @ernstbusch - agree (and wrote about it a little around ClassCastException in my post ;) ), using @SessionAttributes in complicated application can be very tricky, at least with the default Spring setup, but sometimes it is necessary anyway ... Thankfully it can be tuned using your own SessionAttributeStore implementation :)

    @Sandeep and @ernstbusch - thanks for the remarks :)

    ReplyDelete
  3. I see now days every single library is following Annotation based programming model , spring is no exception.

    Thanks
    javin
    Why String is immutable in Java

    ReplyDelete
  4. Thanks for the clear article

    As Warlock said, the current implementation of @SessionAttributes is too bloated for the cleaning step.

    However, as always with Spring, the framework is flexible enough to let you inject your own implementation of SessionAttribureStore.

    Indeed it is very easy to create your own SessionAttribureStore impl. Just create a POJO with a HashMap and set this POJO scope to "session" in the Spring context.

    For proper session cleaning, create your own @SessionClean annotation, set it on the handler method and user Spring AOP to intercept the annotation to clear the session on method exit

    http://doanduyhai.wordpress.com

    ReplyDelete
  5. @warlock
    It seems that in Spring 3.1 @SessionAttributes mechanism has been redesigned. Since Spring 3.1 out-of-box uses RequestMappingHandlerAdapter, ModelFactory and SessionAttributesHandler i haven't faced the problems you mentioned. It can happen that the issues you described are still buried within AnnotationMethodHandlerAdapter (actully within HandlerMethodInvoker and HandlerMethodResolver) but as i stated these are not out-of-box enabled in spring 3.1.
    I did a look at source code since i could not find any detailed description of how @SessionAttributes mechanism works and it seems that nearly all of the issues you pointed out are gone:
    1. haven't check it
    2. SessionAttributesHandler do not use "actual session attribute" in the same way as it was used before (in HandlerMethodResolver). In case you call Sessionstatus#complete all session attributes as specified on @SessionAttributes(...) of executed controller are removed from HttpSession.
    3. -
    4. passing SessionAttributes among controllers works as expected. As soon as both controllers are decorated with SessionAttributes annotation with same value/values, these are "shared" between controllers with no problem. (Obviously you could still end with ClassCastException in case you share 2 class-incompatible objects under same name)

    ReplyDelete
  6. but when i'm using sessionStatus.setComplete() it showing error in my method

    ReplyDelete