1
votes

I have been experimenting with the Spring 3.1 Cache abstraction features and got them to work but we have some user specific data, which I would like to cache using session scoped beans.

My cache-config.xml (imported into applicationContext.xml):

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd    
    http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">

  <!-- Activates various annotations to be detected in bean classes: Spring's @Required and @Autowired, as well as JSR 250's @Resource. -->

  <context:annotation-config />
  <context:component-scan base-package="my.package.whatevs" />

  <!-- <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="false" mode="proxy" /> -->
  <cache:annotation-driven cache-manager="cacheManager" />

   <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
      <set>
        <bean id="defaultCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="defaultCache" />

        <bean id="accountSettingsCache" class="org.springframework.cache.concurrent.ConcurrentMapCache" scope="session">
          <aop:scoped-proxy proxy-target-class="false" />
          <constructor-arg>
            <value>accountSettingsCache</value>
          </constructor-arg>
        </bean>

      </set>
    </property>
  </bean>

</beans>

I do have RequestContextListener and ContextLoaderListener in web.xml. But everytime I try to autowire my cache object I get the following message:

Error creating bean with name 'scopedTarget.accountSettingsCache': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

I do have spring-aop-3.1.1.RELEASE.jar in my classpath. I tried writing a wrapper for ConcurrentMapCache that has a default constructor with no parameters so I can set proxy-target-class to true. I tried declaring them outside the cacheManager and adding it to the list of caches later.

But everytime I try to set it as a property or autowire it in a class (@Service or @Controller) it gives me the same error. It's as if the aop:scoped-proxy is totally ignored.

I also tried ehCache and it worked but it doesn't seem to support session scoped caching. I could also try to write a custom keyGenerator and use the sessionId as part of the key in the cache, but then I would have to manage it's lifecycle, it could have an expiration time but I want finer control over the data in the cache. Any ideas ?

Thanks.

1
I don't think you should be using session-scoped beans within your cache. Just a normal session-scoped bean without the cache should suffice for session-scoped beans. If you want to use a cache, then using regular scoped beans would be fine, and you would use the session-id somewhere in the cache key.Paul Grime
But why won't it let me autowire a session scoped bean into a @Service class ?Balázs Béla
That is possible, but different to the question above. Is this something else? If you want a SimpleCacheManager per session, then make that bean session-scoped. Otherwise just have a session-scoped bean called shoppingCart or whatever you need, and wire that in to your controller. But I can't see why you need a caching layer for just session-scoped beans.Paul Grime
I thought that it would be nice to have separate caches for each entity or service, since @CacheEvict flushes it all and I couldn't find a good way to tell it to remove only certain keys from cache. So this way I could simply session scope beans that contain user data without having to come up with a mechanism to use the session-id as a key and manage the lifecycle of each cache entry. My main problem is the error message above, I get it when I try to autowire a session scoped bean into a singleton one.Balázs Béla
When the cacheManager is created, there is no session. Therefore when Spring tries to create a session-scoped bean it fails, as there is no session at that point in time (application startup). If you want a cache per session, then move scope="session" from accountSettingsCache to cacheManager. Then when an MVC controller asks for a cacheManager, it will get one specific to the current session. I'm still not sure that a cache is the right thing for you to use, but I don't know your use-case.Paul Grime

1 Answers

0
votes

<bean id="sessionLevelCacheManager" class="org.springframework.cache.support.SimpleCacheManager"
    scope="session">
    <property name="caches">
        <set>
            <bean id="sessionCache"
                class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                p:name="sessionCache">
            </bean>
        </set>
    </property>
    <aop:scoped-proxy proxy-target-class="false" />
</bean>

<bean id="compositeCacheManager" class="org.springframework.cache.support.CompositeCacheManager">
    <property name="cacheManagers">
        <array>
            <ref bean="applicationLevelCacheManager" />
            <ref bean="sessionLevelCacheManager" />
        </array>
    </property>
    <property name="fallbackToNoOpCache" value="true" />
</bean>