Spring 2 "scope" goodness for ColdSpring

In Spring 1.2 (and ColdSpring, which emulates it), you have the "singleton" attribute, which was a boolean flag for whether a bean is a singleton (the default) or a prototype (instantiated afresh for every getBean call).  If you've used Spring 2.0+, you've probably come across the "scope" attribute, which supersedes the "singleton" attribute, and allows singleton, prototype, request, and session lifecycles.

In most cases, singleton and prototype are all you need, but it's occasionally useful to scope a bean to a request or a session.  But what does that mean, exactly?

With a prototype bean, you get a new instance back every time you call getBean.  With a singleton bean, you get the same instance back every time you call getBean for the life of the BeanFactory.  With a request bean, you get the same instance back every time you call getBean for the life of the request.  You can probably guess what happens for a session bean.

Since ColdSpring emulates the Spring 1.2 way of doing things, you don't have access to request- or session-scoped beans, unless you manually load them into the appropriate scope and always reference them from there, instead of the BeanFactory.  That's a mess, so I wrote a simple wrapper bean called BeanScopeCache to provide this functionality in bean form.

To use it, you define your target bean as you normally do (ensuring you set singleton="false"):

<bean id="requestConfig_target"
    class="com.barneyb.cache.requestconfig"
    singleton="false">
  <constructor-arg name="contentcache"><ref bean="contentCache" /></constructor-arg>
  <constructor-arg name="publicUrl"><value>${publicUrl}</value></constructor-arg>
</bean>

and then create a BeanScopeCache as such:

<bean id="requestConfig" class="com.mentor.util.BeanScopeCache">
  <constructor-arg name="targetBeanName">
    <value>requestConfig_target</value>
  </constructor-arg>
  <constructor-arg name="scope">
    <value>request</value>
  </constructor-arg>
</bean>

In this case, my target bean is named "contentCacheRequestConfigTarget" and through BeanScopeCache it'll be tied to the request scope.  Note that you can't use a nested bean, because BeanScopeCache needs a bean name not an injected instance.  Here's the source:

<cfcomponent output="false" extends="coldspring.beans.factory.FactoryBean">

  <cfset SCOPE_KEY = "__com_mentor_util_bean_scope_factory_cache_key__" />

  <cffunction name="init" access="public" output="false" returntype="BeanScopeCache">
    <cfargument name="targetBeanName" type="string" required="true" />
    <cfargument name="scope" type="string" required="true" />
    <cfargument name="bindToBeanFactory" type="string" default="false"
      hint="Whether to bind instances to this bean factory.  Ignored for singletons." />
    <cfset variables.targetBeanName = targetBeanName />
    <cfset variables.scope = scope />
    <cfset variables.bindToBeanFactory = bindToBeanFactory />
    <cfreturn this />
  </cffunction>

  <cffunction name="setBeanFactory" access="public" output="false" returntype="void">
    <cfargument name="beanFactory" type="coldspring.beans.BeanFactory" required="true" />
    <cfset variables.beanFactory = beanFactory />
    <cfset INSTANCE_ID = createObject("java", "java.lang.System").identityHashCode(beanFactory) />
  </cffunction>

  <cffunction name="isSingleton" access="public" output="false" returntype="boolean">
    <cfreturn scope EQ "singleton" />
  </cffunction>

  <cffunction name="getObject" access="public" output="false" returntype="any">
    <cfset var key = SCOPE_KEY & targetBeanName />
    <cfset var container = "" />
    <cfif bindToBeanFactory>
      <cfset key &= INSTANCE_ID />
    </cfif>
    <cfswitch expression="#scope#">
      <cfcase value="prototype">
        <cfset container = {} />
      </cfcase>
      <cfcase value="request">
        <cfset container = request />
      </cfcase>
      <cfcase value="session">
        <cfset container = session />
      </cfcase>
      <cfcase value="singleton">
        <cfset container = variables />
      </cfcase>
      <cfdefaultcase>
        <cfthrow type="IllegalArgumentException"
          message="The '#scope#' scope is not supported." />
      </cfdefaultcase>
    </cfswitch>
    <cfif NOT structKeyExists(container, key)>
      <cfset container[key] = beanFactory.getBean(targetBeanName) />
    </cfif>
    <cfreturn container[key] />
  </cffunction>

</cfcomponent>

4 responses to “Spring 2 "scope" goodness for ColdSpring”

  1. Steve 'Cutter' Blades

    Sweet! Can't wait to get a chance to play around with it. Thanks Barney!

  2. Joseph Lee Hunsaker

    Now let's see this for a RemoteFactoryBean :)

  3. Joseph Lee Hunsaker

    This seems to work for RemoteFactoryBean

    simply change the extends to "ColdSpring.aop.framework.RemoteFactoryBean"

    and add the following to getObject after "

  4. Joseph Lee Hunsaker

    This seems to work for RemoteFactoryBean

    simply change the extends to "ColdSpring.aop.framework.RemoteFactoryBean"

    and add the following to getObject after '<cfset var container = "" />'

    <cfif not isConstructed()>
    <cfset createRemoteProxy() />
    </cfif>