Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

Motivation

Spring currently does currently not support session scoped beans/components out of the box. You can decide between singleton or prototype lifecycle, but not having you cannot have your beans bound to the session lifecycle of web applications. There are plans for integrating such a feature in the Spring 12.3 release, but this is not confirmed and there is no schedule0 release.
We will try to point out some possible workarounds for your webwork WebWork based applications. First we look at the general solutions found among the Spring community, dealing with HTTPSession and all that. After that we will discuss the special conditions and requirements found in XWork/WebWork and how that might affect possible solutions. We will show some XWork/WebWork specific solutions for the given problem.

Info

The first milestone of Spring 2.0 (formerly 1.3) will be released the second week of December 2005. It is confirmed that it does contain Session Scope components using the Proxy (CGLIB or JDK) approach.

General Solutions for Webapplications

Custom TargetSource with a ServletFilter

A quite "clean" solution for web applications in general can be found at JA-SIG. The solution is well documented and can be found here. Below you will find a WebWork adoption of this solution.

The Spring The Spring 2.0 Way

Interface21 added support for session (and request) scoped beans in Spring 2.0. This approach creates a CGLIB or JDK Dynamic proxy of the session scoped bean using the org.springframework.aop.target.scope.ScopedProxyFactoryBean and setting the scopeMap to org.springframework.web.context.scope.SessionScopeMap.

Since the jars are backwards compatible simply build Spring and replace the jars shipped with WebWork. (Spring Since the jars are backwards compatible simply build spring (Sprng 2.0 M1 should be out by the time you read this.) and use the aop:scope parameters to scope your beans. You can find a modified applicationContext.xml for the shopping cart example below.

There are 2 ways to set this up depending upon whether or not XML simplification is used. The first method uses the traditional bean definitions and is useful to understand what is happening under the covers.

A modified applicationContext.xml for the shopping cart example using the traditional XML DTD is below.

Code Block
xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <bean id="shoppingCart" class="catalogorg.springframework.aop.target.scope.ScopedProxyFactoryBean"
            singleton="truefalse">
        <property name="scopeKey" classvalue="com.opensymphony.webwork.example.ajax.catalog.TestCatalog" shoppingCart"/>

        <bean id<property name="targetBeanName" value="__shoppingCart"/>
        <property  singletonname="truescopeMap">
            <bean class="com.opensymphony.webwork.example.ajax.cart.DefaultCart" />


</beans>
org.springframework.web.context.scope.SessionScopeMap"/>
        </property>
    </bean>

    <bean id="__shoppingCart" class="com.opensymphony.webwork.example.ajax.cart.DefaultCart"
            singleton="false"/>

</beans>

A modified applicationContext.xml for the shopping cart example using the XML simplification is below.

Code Block
xml
xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="catalog" class="com.opensymphony.webwork.example.ajax.catalog.TestCatalog"
          singleton="true"/>

    <bean id="shoppingCart" class="com.opensymphony.webwork.example.ajax.cart.DefaultCart"
          singleton="true">
        <aop:scope type="session"/>
    </bean>

</beans>

You will also need to modify the web.xml to include the following filter.

Code Block
xml
xml

<filter>
    <filter-name>springFilter</filter-name>
    <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>springFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Custom TargetSource with a ServletFilter

A quite "clean" solution for web applications in general can be found at JA-SIG. The solution is well documented and can be found here. Below you will find a WebWork adoption of this solution.

XWork/WebWork specific solutions

...

WebWork is based on XWork, and XWork is not tied to the web layer. So when dealing with session scoped stuffobjects, WW users might want to use XWorks XWork's session abstraction features to keep their application independent from the web context. This is why we will discuss some XW/WW specific solutions below.

...

Here is a modified version of the TargetSource solution pointed out above that integrates with the existing WebWork session so and doesn't require an additional filter or listener. Usage is pretty much the same, create an interface for your object and make sure that you always use that interface and not the underlying implementation or autowiring will fail. You can find more information on how to make this work by looking at the WebWorkTargetSource Shopping Cart Example.

Code Block
titleWebWorkTargetSource.java
borderStylesolid
package org.tuxbot.webwork.spring;

/* Portions Copyright 2005 The JA-SIG Collaborative.  All rights reserved.
 *  See license distributed with this file and
 *  available online at http://www.uportal.org/license.html
 */

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.target.AbstractPrototypeBasedTargetSource;
import org.springframework.beans.factory.DisposableBean;
import com.opensymphony.xwork.ActionContext;

import java.util.Map;

/**
 * This target source is to be used in collaberation with WebWork.
 * The target source binds the target bean to the Session retrieved from
 * WebWork. By default the bean is bound to the session
 * using the name of the target bean as part of the key. This can be overridden by setting
 * the <code>sessionKey</code> property to a not null value.
 *
 * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
 * @author Eric Molitor <a href="mailto:eric@tuxbot.com">eric@tuxbot.com</a>
 * @version 1.0
 */
public class WebWorkTargetSource extends AbstractPrototypeBasedTargetSource implements DisposableBean {
    private final static Log LOG = LogFactory.getLog(WebWorkTargetSource.class);

    private transient Object noSessionInstance = null;
    private String sessionKey = null;
    private String compiledSessionKey = null;

    public WebWorkTargetSource() {
        this.updateBeanKey();
    }

    /**
     * @return Returns the sessionKey.
     */
    public String getSessionKey() {
        return this.sessionKey;
    }
    /**
     * @param sessionKey The sessionKey to set.
     */
    public void setSessionKey(String sessionKey) {
        this.sessionKey = sessionKey;
        this.updateBeanKey();
    }
    /**
     * @see org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource#setTargetBeanName(java.lang.String)
     */
    public void setTargetBeanName(String targetBeanName) {
        super.setTargetBeanName(targetBeanName);
        this.updateBeanKey();
    }

    /**
     * @see org.springframework.aop.TargetSource#getTarget()
     */
    public Object getTarget() throws Exception {
        final Map session = ActionContext.getContext().getSession();

        if (session == null) {
            LOG.warn("No Session found for thread '" + Thread.currentThread().getName() + "'");

            if (this.noSessionInstance == null) {
                this.noSessionInstance = this.newPrototypeInstance();

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created instance of '" + this.getTargetBeanName() + "', not bound to any webWorkSession.");
                }
            }
            else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Found instance of '" + this.getTargetBeanName() + "', not bound to any webWorkSession.");
                }
            }

            return this.noSessionInstance;
        }
        else {
            String beanKey = this.compiledSessionKey;

            Object instance = session.get(beanKey);
            if (instance == null) {
                instance = this.newPrototypeInstance();
                session.put(beanKey, instance);

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created instance of '" + this.getTargetBeanName() + "', bound to webWorkSession for '" 
                       + Thread.currentThread().getName() + "' using key '" + beanKey + "'.");
                }
            }
            else if (LOG.isDebugEnabled()) {
                LOG.debug("Found instance of '" + this.getTargetBeanName() + "', bound to webWorkSession for '" 
                       + Thread.currentThread().getName() + "' using key '" + beanKey + "'.");
            }

            return instance;
        }
    }

    /**
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    public void destroy() throws Exception {
        if (this.noSessionInstance != null && this.noSessionInstance instanceof DisposableBean) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Destroying sessionless bean instance '" + this.noSessionInstance + "'");
            }

            ((DisposableBean)this.noSessionInstance).destroy();
        }
    }

    /**
     * Generates the key to store the bean in the session with.
     */
    private void updateBeanKey() {
        if (this.sessionKey == null) {
            final StringBuffer buff = new StringBuffer();

            buff.append(this.getClass().getName());
            buff.append("_");
            buff.append(this.getTargetBeanName());

            this.compiledSessionKey = buff.toString();
        }
        else {
            this.compiledSessionKey = this.sessionKey;
        }
    }
}

...