Spring session bean shared between HttpSession-s

1.8k views Asked by At

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?

2

There are 2 answers

0
Sotirios Delimanolis On BEST ANSWER

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.

0
a better oliver On
public class HomeController {
  @Autowired 
  private Cart cart; <-- Proxy

The Cart instance that gets injected into the HomeController instance is only a proxy that delegates method calls to the "real" instance. The Cart class itself has no own method or state yet, so you won't notice any difference between sessions, of course.