Spring Cache Abstraction VS interfaces VS key param (“Null key returned for cache operation” error)

cachingillegalargumentexceptioninterfacekeyspring

While implementing, I came across a problem with Spring Cache Abstraction VS interfaces.
Lets say I have the following interface:

package com.example.cache;

public interface IAddItMethod 
{   
    Integer addIt(String key);
}

And the two following implementations:

package com.example.cache;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class MethodImplOne implements IAddItMethod 
{
    @Override
    @Cacheable(value="integersPlusOne", key="#keyOne")
    public Integer addIt(String keyOne) 
    {
        return new Integer(Integer.parseInt(keyOne) + 1);
    }
}

.

package com.example.cache;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class MethodImplTwo implements IAddItMethod 
{
    @Override
    @Cacheable(value="integersPlusTwo", key="#keyTwo")
    public Integer addIt(String keyTwo)
    {
        return new Integer(Integer.parseInt(keyTwo) + 2);
    }
}

Note that the IAddItMethod is not the one specifying @Cacheable. We could have other implementation (ex MethodImplThree) without the @Cacheable annotation.

We’ve got a simple beans.xml with:

context:component-scan base-package="com.example.cache"

Adding to that, two jUnit test cases:

package com.example.cache;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = {"classpath:beans.xml"})  
public class MethodImplOneTest 
{

    @Autowired
    @Qualifier("methodImplOne")
    private IAddItMethod classUnderTest;

    @Test
    public void testInit() 
    {
        int number = 1;
        assertEquals(new Integer(number + 1), classUnderTest.addIt("" + number));
    }

}

.

package com.example.cache;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = {"classpath:beans.xml"})  
public class MethodImplTwoTest 
{

    @Autowired
    @Qualifier("methodImplTwo")
    private IAddItMethod classUnderTest;

    @Test
    public void testInit() 
    {
        int number = 1;
        assertEquals(new Integer(number + 2), classUnderTest.addIt("" + number));
    }

}

When I run the tests individually, they succeed.
However, if I run them both together (selecting the package, right-click, run as), the second one (not necessarily MethodImplTwoTest, just the second one running) will fail with the following exception:

java.lang.IllegalArgumentException: Null key returned for cache operation (maybe you are using named params on classes without debug info?) CacheableOperation[public java.lang.Integer com.example.cache.MethodImplOne.addIt(java.lang.String)] caches=[integersPlusOne] | condition='' | key='#keyOne'
    at org.springframework.cache.interceptor.CacheAspectSupport.inspectCacheables(CacheAspectSupport.java:297)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:198)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:66)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy16.addIt(Unknown Source)
    at com.example.cache.ITMethodImplOneIntegrationTest.testInit(ITMethodImplOneIntegrationTest.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

note: I'm using Eclipse STS 3.0 and the "Add variable attributes to generated class files" is enabled.

IMPORTANT: If I don't specify the "key" in the @Cacheable annotations, it works.

Is there anything I forgot to specify? config? annotations?

Thanks in advance!

Best Answer

My guess is that for jdk proxy the parameter name is fetched from the interface method so it's key and not keyTwo.

update: You can try to use parameter indexes instead

If for some reason the names are not available (ex: no debug information), the parameter names are also available under the p<#arg> where #arg stands for the parameter index (starting from 0).

see http://static.springsource.org/spring/docs/3.1.0.M1/spring-framework-reference/html/cache.html#cache-spel-context

Related Topic