Spring Memcached annotations not Caching

2.4k views Asked by At

I'm trying to get Memcache working in Spring.

I've setup a local Memcached server using Docker and Kitematic:

enter image description here

I can access the Memcached server using telnet: telnet 192.168.99.100 32780 and then run stats or stats items (which only prints out END if the cache is empty);

My pom.xml:

    <dependency>
        <groupId>com.google.code.simple-spring-memcached</groupId>
        <artifactId>simple-spring-memcached</artifactId>
        <version>3.6.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.simple-spring-memcached</groupId>
        <artifactId>spring-cache</artifactId>
        <version>3.6.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.simple-spring-memcached</groupId>
        <artifactId>xmemcached-provider</artifactId>
        <version>3.6.0</version>
    </dependency>

In my applicationContext.xml I have the following:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:rabbit="http://www.springframework.org/schema/rabbit"
    xmlns:task="http://www.springframework.org/schema/task"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd         
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
        http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd">

    <context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
    ...
    <import resource="cacheContext.xml" />
    ...

In cacheContext.xml my config is as follow:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <aop:aspectj-autoproxy/>
    <cache:annotation-driven/>

    <context:component-scan base-package="com.google.code.ssm"/>
    <context:component-scan base-package="com.mycee.application"/>

    <bean id="cacheBase" class="com.google.code.ssm.aop.CacheBase"/>

    <bean id="readThroughSingleCache" class="com.google.code.ssm.aop.ReadThroughSingleCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="readThroughMultiCache" class="com.google.code.ssm.aop.ReadThroughMultiCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="readThroughAssignCache" class="com.google.code.ssm.aop.ReadThroughAssignCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="updateSingleCache" class="com.google.code.ssm.aop.UpdateSingleCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="updateMultiCache" class="com.google.code.ssm.aop.UpdateMultiCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="updateAssignCache" class="com.google.code.ssm.aop.UpdateAssignCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="invalidateSingleCache" class="com.google.code.ssm.aop.InvalidateSingleCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="invalidateMultiCache" class="com.google.code.ssm.aop.InvalidateMultiCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="invalidateAssignCache" class="com.google.code.ssm.aop.InvalidateAssignCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>

    <bean id="incrementCounterInCache" class="com.google.code.ssm.aop.counter.IncrementCounterInCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="decrementCounterInCache" class="com.google.code.ssm.aop.counter.DecrementCounterInCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="readCounterFromCache" class="com.google.code.ssm.aop.counter.ReadCounterFromCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>
    <bean id="updateCounterInCache" class="com.google.code.ssm.aop.counter.UpdateCounterInCacheAdvice">
        <property name="cacheBase" ref="cacheBase"/>
    </bean>

    <bean name="cacheManager" class="com.google.code.ssm.spring.SSMCacheManager">
        <property name="caches">
            <set>
                <bean class="com.google.code.ssm.spring.SSMCache">
                    <constructor-arg name="cache" index="0" ref="defaultCache"/>
                    <constructor-arg name="expiration" index="1" value="300"/>
                    <constructor-arg name="allowClear" index="2" value="false"/>
                </bean>
            </set>
        </property>
    </bean>

    <bean name="defaultCache" class="com.google.code.ssm.CacheFactory" depends-on="cacheBase">
        <property name="cacheName" value="defaultCache"/>
        <property name="cacheClientFactory">
            <bean class="com.google.code.ssm.providers.xmemcached.MemcacheClientFactoryImpl"/>
        </property>
        <property name="addressProvider">
            <bean class="com.google.code.ssm.config.DefaultAddressProvider">
                <property name="address" value="localhost:11211"/>
            </bean>
        </property>
        <property name="configuration">
            <bean class="com.google.code.ssm.providers.CacheConfiguration">
                <property name="consistentHashing" value="true"/>
            </bean>
        </property>
    </bean>


</beans>

I've created three different methods, each using different caching mechanisms:

@Component("cacheEndpoint")
public class CacheClass {

    @Autowired
    SSMCacheManager cache;

    public String getDateTime1(String anything) {

        SSMCache c = cache.getCache("defaultCache");

        String s = c.get(anything, String.class);
        if (s != null) {
            return s;
        }

        Date d = new Date();
        String response = d.toString() + " - " + d.getTime();
        c.put(anything, response);

        return response;

    }

    @Cacheable("defaultCache")
    public String getDateTime2(String anything) {
        Date d = new Date();
        String response = d.toString() + " - " + d.getTime();
        return response;
    }

    @ReadThroughSingleCache(namespace = "defaultCache", expiration = 15000)
    public String getDateTime3(String anything) {
        Date d = new Date();
        String response = d.toString() + " - " + d.getTime();
        return response;
    }


}

To access it I do:

@Autowired
CacheClass c;

...

// caches perfectly
c.getDateTime1("test");

// doesn't do any caching
c.getDateTime2("test");

// doesn't do any caching
c.getDateTime3("test");

After placing runtime exceptions in getDateTime2 and getDateTime3, it was established that the interceptors aren't being invoked.

Any idea what could be the cause of @Cachable and @ReadThroughSingleCache not doing their interception magic?

Update based on Matjaž Pečan's response:

CacheClass Interface:

public interface CacheClass {

    public String getDateTime1(String anything);

    public String getDateTime2(String anything);

    public String getDateTime3(String anything);

}

CacheClass Implementation:

@Component("cacheEndpoint")
public class CacheClassImpl implements CacheClass {

    @Autowired
    SSMCacheManager cache;

    public String getDateTime1(String anything) {

        SSMCache c = cache.getCache("defaultCache");

        String s = c.get(anything, String.class);
        if (s != null) {
            return s;
        }

        Date d = new Date();
        String response = d.toString() + " - " + d.getTime();
        c.put(anything, response);

        return response;

    }

    @Cacheable("defaultCache")
    public String getDateTime2(String anything) {

        Date d = new Date();
        String response = d.toString() + " - " + d.getTime();
        return response;

    }

    @ReadThroughSingleCache(namespace = "defaultCache", expiration = 15000)
    public String getDateTime3(String anything) {

        Date d = new Date();
        String response = d.toString() + " - " + d.getTime();
        return response;

    }


}

SOAP Endpoint where I'm testing the cache:

@Endpoint
public class PingEndpoint {

    @Autowired
    CacheClass c;

    @ResponsePayload
    @PayloadRoot(localPart = "PingRequest", namespace = "http://www.mycee.com/Application")
    public PingResponse doPing(@RequestPayload PingRequest request) {

        // caches perfectly
        System.out.println(c.getDateTime1("test"));

        // doesn't do any caching
        System.out.println(c.getDateTime2("test"));

        // doesn't do any caching
        System.out.println(c.getDateTime3("test"));

    }
}

cacheContext.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:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <cache:annotation-driven/>

    ...
2

There are 2 answers

9
ragnor On BEST ANSWER

There is an error in SSM 3.6.0, please downgrade to 3.5.0 to solve the issue or try to add

depends-on="cacheBase"

to defaultCache bean definition.

Update 1

Self invocations don't work. A call won't be intercepted and result won't be cached if a invocation is through this object. Make sure that method defined in a bean is invoked from another Spring bean.

Update 2

For SSM method has to be annotated as below:

@ReadThroughSingleCache(namespace = "defaultCache", expiration = 15000)
public String getDateTime3(@ParameterValueKeyProvider String anything) {
     ...
}

Still interceptors for some reasons aren't triggered.

3
Matjaž Pečan On

Spring proxying mechanisms differ depending on which implementation is used.

The default is AspectJ which requires interfaces (proxy-by-interface) and those interfaces will then be implemented by the actual proxy wrapped around your bean. Since your bean is only a class and does not have an interface, it is not proxied with AspectJ.

There are two possible solutions:

  • Implement an interface for CacheClass and use that interface in wiring to other beans
  • Use CGLib proxying (you will need to add a runtime dependency on CGLib) and add proxy-target-class in your aspectj-proxy element:

    <aop:aspectj-autoproxy proxy-target-class="true"/>