user3812776
user3812776

Reputation: 13

Spring 4 cache key generation is not working as expected

After upgrading our spring to version 4, our custom key generator stopped working. Before the migration, our code that overrides "generate" method was executed, but after migrating to spring 4.0.5, the code is not executed at all. Instead I saw that SimpleKeyGenerator is the one that is always executed. Is this a bug in spring? Why I can't override the generate method with my own code as I used to in previous versions?

sample from the root context:

<cache:annotation-driven key-generator="cacheKeyGenerator" />
<bean id="cacheKeyGenerator" class="com.poalim.xp.general.cache.CacheKeyGenerator"/>

sample from the java key generation (before the migration)

public class CacheKeyGenerator extends DefaultKeyGenerator implements ApplicationContextAware {
public Object generate(Object target, Method method, Object... params) { 
    return  method.getName() + super.generate(target, method, params); 
}

}

sample code after the migration

public class CacheKeyGenerator extends SimpleKeyGenerator implements ApplicationContextAware {
public Object generate(Object target, Method method, Object... params) { 
    return  method.getName() + super.generate(target, method, params); 
}

}

Additional information: After debugging the code I saw that every time "generate" method is called, it is executed ONLY in SimpleKeyGenerator and NOT in my custom CacheKeyGenerator class. I tried to understand why, so I made some debugging. While debuging, I saw that there is org.springframework.cache.interceptor.CacheAspectSupport class, that has a private property: private KeyGenerator keyGenerator = new SimpleKeyGenerator(); This class has a setter method on keyGenerator property and I saw that when the context is started, this setter method is called with my custom CacheKeyGenerator, so I conclude that my configuration is correct and the problem is not in the configuration. I also saw that when key ganeration is needed, the keyGenerator property "lost" the "CacheKeyGenerator" value and has "SimpleKeyGenerator". This explains why my custom code is never executed, but I don't understand why the keyGenerator propery point to SimpleKeyGenerator. It seems like a SPring BUG. What is the problem?

Upvotes: 1

Views: 4314

Answers (1)

Phil Webb
Phil Webb

Reputation: 8622

Having looked at the sample code that you emailed I see that you have two issues:

@EnableCaching

The @EnableCaching annotation is designed to be used as a replacement for the XML configuration when you are using Java config. For example:

 @Configuration
 @EnableCaching
 public class AppConfig implements CachingConfigurer {

     @Bean
     @Override
     public CacheManager cacheManager() {
         // configure and return an implementation of Spring's CacheManager SPI
         SimpleCacheManager cacheManager = new SimpleCacheManager();
         cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
         return cacheManager;
     }

     @Bean
     @Override
     public KeyGenerator keyGenerator() {
         // configure and return an implementation of Spring's KeyGenerator SPI
         return new MyKeyGenerator();
     }

 }

Since you are using XML configuration:

<cache:annotation-driven key-generator="cacheKeyGenerator" />

You should not have @EnableCaching annotations anywhere in your code as it can override your XML (remove it from FacadeImpl).

Component Scan Override

You have a root-context.xml and a servlet-context.xml configuration however both of these are scanning the same package. You cache configuration is declared in the root-context.xml, so beans in this context have caching applied, however, beans are duplicated (because they are scanned again) in your servlet-context.xml where no caching is applied.

Since you don't seem to really need a root/web separation I would recommend just creating a single ApplicationContext. To do this:

1) Remove the following lines from your web.xml

<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:root-context.xml</param-value>
</context-param>

<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

2) Import your root-context.xml file directly by adding the following to your servlet-context.xml

<beans:import resource="classpath:root-context.xml"/>

Tip

One final thing that does look a little odd is that you are relying on the toString() method of the key. I would be tempted to use the SimpleKey class directly for your needs:

public Object generate(Object target, Method method, Object... params) {
    return new SimpleKey(method.getName(), params);
}

You're also much more likely to get help with issues like this if you can publicly provide sample code that replicates the issue and make it available as link from stack overflow. GitHub is an excellent place to host sample projects. Also please don't shout that it is BUG until you are sure :)

Upvotes: 2

Related Questions