javaspringspring-mvcsession-bean

Spring session bean shared between HttpSession-s


I'm trying to undestand session beans in Spring. The book I'm reading says, about them, that:

the bean is created when needed and stored in the javax.servlet.http.HttpSession. When the session is destoyed so is the bean instance.

I tried with the following example:

The bean:

package com.at.test.web;

public class Cart {
    public static int dummy = 0;
    public Cart() {
        System.out.println("Cart::<init> with hashCode " + hashCode());
    }
}

The bean definition:

<beans:bean id="cartBean" class="com.at.test.web.Cart" scope="session">
    <apo:scoped-proxy/>
</beans:bean>

The controller:

@Controller
public class HomeController {
    @Autowired 
    private Cart cart;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(HttpSession session, Model model) {
      System.out.println("Cart is: " + cart.hashCode() 
          + " ; dummy = " + (cart.dummy++) 
          + " (" + cart.getClass().getCanonicalName() + ")" 
          + "; session is: " + session.hashCode());
      return "home.jsp";
  }
}

This is what happens on Tomcat startup:

Cart::<init> with hashCode 970109301

I think Spring need this instance in order to create the CGLIB proxies. Anyway, I'm not that sure.

After the startup, I use two different browsers to have two different HttpSession. The result, when the controller is invoked, is:

Cart is: 578093288 ; dummy = 0 (com.at.test.web.Cart$$EnhancerByCGLIB$$2eb8334f); session is: 1013723725
Cart is: 578093288 ; dummy = 1 (com.at.test.web.Cart$$EnhancerByCGLIB$$2eb8334f); session is: 1060682497

The cart instance seems to be shared between HttpSession-s, but I was expecting two instances of Cart.

The same if I let the bean implements an interface, use annotation-driven approch and component scan:

public interface ICart {}

--

@Component
@Scope(value="session", proxyMode=ScopedProxyMode.INTERFACES)
public class Cart implements ICart {
    public static int dummy = 0;
    public Cart() {
        System.out.println("Cart::<init> with hashCode " + hashCode());
    }
}

Am I missing something? Did I misunderstood the meaning of session bean?


Solution

  • There's a lot of proxying and delegation going on.

    This field

    @Autowired 
    private Cart cart;
    

    where Cart is session scoped will be proxied as you can see in your logs

    com.at.test.web.Cart$$EnhancerByCGLIB$$2eb8334f
    

    But this proxy is not wrapping a Cart object. It is wrapping a SimpleBeanTargetSource which takes care of getting a Cart bean from the BeanFactory.

    Your bean definition attaches a SessionScope object which is responsible for checking your HttpSession through a static ThreadLocal field in RequestContextHolder. When request is made to get a bean from the BeanFactory, it will delegate the action to the SessionScope object which will check the HttpSession, use the one there if it exists or create and register a new one if it doesn't.

    You don't notice this with your test because

    cart.hashCode()
    

    delegates to the SimpleBeanTargetSource object (incorrectly, if you ask me). But if you did

    cart.toString();
    

    you would see the difference as that actually reaches the underlying Cart object.


    Depending on the scope and proxying strategy you use, all this may be different, but the end goal is still achieved.