Spring in Practice

Willie Wheeler's Spring blog

Session-scoped Beans in Spring

| Comments

This tutorial will show you how to define session-scoped beans in Spring. The idea here is that you have a web application, and you have various objects that exist on a per-session basis, such as maybe a user profile or a shopping cart. You’d like to make those available to some service bean, say, without having to manually pull them off the session and pass them as method arguments every time. First I’ll show you how to do that. After that I’ll talk a little bit about whether I think it’s a good idea.

Here’s the code for this article: ssb-example.zip

The code uses annotation-based autowiring. If you are unfamiliar with that and it’s not reasonably clear what I’m doing with the code, you might want to see my articles Annotation-Based Autowiring in Spring 2.5 and Annotation-Based MVC in Spring 2.5.

Dependencies

I’m using Java 6 (though Java 5 should work too) and Spring 2.5.4 (though Spring 2.5.x should work generally). You will need the following JARs:

  • cglib-nodep-2.1_3.jar
  • commons-logging.jar
  • spring.jar
  • spring-webmvc.jar

Java classes

We have four classes: a controller, a service interface, a service implementation, and a model class.

First, the controller:

package ssbexample;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MyController {
    private String SHOPPING_CART_KEY = "shoppingCart";
    
    private MyService service;

    public MyService getService() {
        return service;
    }

    public void setService(MyService service) {
        this.service = service;
    }
    
    @RequestMapping("/viewcart.do")
    public ModelMap viewCart() {
        return new ModelMap(SHOPPING_CART_KEY, service.getShoppingCart());
    }
    
    @RequestMapping("/additem.do")
    public ModelAndView addItem() {
        service.addItem();
        ShoppingCart cart = service.getShoppingCart();
        return new ModelAndView("/viewcart", SHOPPING_CART_KEY, cart);
    }
}

The thing to notice about the controller code is that I’m grabbing the shopping cart from the service bean itself, rather than pulling it off of the session directly. That means that the service bean is somehow able to return a session-specific shopping cart. (And we’ll see how to do that shortly.) We’re also able to tell the service to add an item (to the cart), and the service once again knows exactly which cart to add the item to.

Now here’s the service interface:

package ssbexample;

public interface MyService {
    
    ShoppingCart getShoppingCart();
    
    void addItem();
}

Here’s the service implementation:

package ssbexample;

import org.springframework.stereotype.Service;

@Service("service")
public class MyServiceImpl implements MyService {
    private ShoppingCart shoppingCart;

    public ShoppingCart getShoppingCart() {
        return shoppingCart;
    }
    
    public void setShoppingCart(ShoppingCart shoppingCart) {
        this.shoppingCart = shoppingCart;
    }
    
    public void addItem() {
        shoppingCart.addItem();
    }
}

Notice that with the shopping cart getter and setter, we have a way to inject a shopping cart into the service bean. But we have only a single service bean instance, so it looks a little bit like magic right now.

And finally, here’s the model class itself (a simple shopping cart):

package ssbexample;

import java.io.Serializable;

public class ShoppingCart implements Serializable {
    private int numItems;
    
    public ShoppingCart() {
    }
    
    public int getNumItems() {
        return numItems;
    }
    
    public void addItem() {
        numItems++;
    }
}

As you may have guessed, the shopping cart is the object we want to save on the session. I’ve implemented Serializable so the servlet container can save the session when you shut it down. (Tomcat does that, anyway.)

The JSP

We have just one JSP.

<html>
    <head>
        <title>View Shopping Cart</title>
    </head>
    <body>
        <h1>View Shopping Cart</h1>
        <p>Your shopping cart currently contains ${shoppingCart.numItems} items.</p>
        <p><a href="additem.do">Add item to cart</a></p>
    </body>
</html>

web.xml configuration file

Nothing special here:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    
    <servlet>
        <servlet-name>front</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>front</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

Now let’s look at our Spring configuration file, which is where the aforementioned magic happens.

Spring application context

Here’s our Spring config:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-2.5.xsd"
    default-autowire="byName">
    
    <!-- Scan for controllers and services -->
    <context:component-scan base-package="ssbexample"/>
    
    <!-- Create a proxy to generate session-scoped shopping carts -->
    <bean id="shoppingCart" class="ssbexample.ShoppingCart" scope="session">
        <!-- This requires CGLIB -->
        <aop:scoped-proxy/>
    </bean>
    
    <!-- Maps a logical view name to a physical resource -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

As I noted at the outset, we’re autowiring here, so I’ll just point out that the shoppingCart bean is automatically injected into the service bean (identified by the @Service(“service”) annotation inside ssbexample.MyServiceImpl) since I have default-autowire=“byName” set in the Spring config.

On the shoppingCart bean, we have the scope set to session, which is probably unsurprising. But that’s not the whole story.

The interesting thing about the injection is that we’re not injecting any particular shopping cart into the service bean. Rather we’re injecting a web-aware proxy into the service bean. That’s what <aop:scoped-proxy/> is for. The proxy, being web-aware, can see individual user sessions. So when any particular user asks for a shopping cart, the proxy grabs the shopping cart off the current session and returns it.

Note that in order to use session scope, you have to be using a web-aware Spring application context, such as XmlWebApplicationContext. Otherwise there’s no way for the scoped proxy to reference a current session.

Discussion

I think that this is a pretty nice tool to have in your toolbox. Using this technique, you can move the creation of session-scoped beans into the Spring application context, instead of having to create those manually in the code, and then having to place them manually on the session. And it does make the service API cleaner, because you can avoid having to include important objects (like a shopping cart) in all the method signatures. So I like that about session-scoped beans.

The main reservation I have is that despite appearances, there’s an important sense in which using session scoping ties the code to Spring, which goes against the whole Spring philosophy of being noninvasive. Usually service beans are designed and implemented such that a single service bean serves multiple clients, each with its own client session. And as long as we’re able to inject a web-aware shopping cart proxy into the shopping cart slot, we haven’t abandoned that. But that’s the problem: if we decide to move away from Spring, we may find ourselves without easy access to a web-aware shopping cart proxy, and so we’re forced to redesign the service bean in some way (such as changing the service methods to include a shopping cart parameter).

I don’t think this concern invalidates the approach, but it’s important to understand its ramifications.

Post migrated from my Wheeler Software site.

Comments