Spring Cache - Create custom CacheManager

Oleg picture Oleg · Jun 13, 2016 · Viewed 18.4k times · Source

I'm using Spring Boot and EhCache to develop a calendar application. I'm trying to cache the following method:

@Override
@Cacheable(value = "concerts")
public List<Event> getEvents(String eventsForUser, Date startDate, Date endDate) throws Exception {
    return fetchEventsFromTheServer(eventsForUser, startDate, endDate);
}

The challenge is I would like to manipulate returned cached result. For example, check if there is cache for given dates but for a different user and then return it instead (as long as both users meet certain criteria).

So, before returning a result I would like:

  • to get a list of all cached entries
  • loop through all of them and check for needed dates/users
  • if found suitable - return that
  • if not found - cache is not available, run the method.

I think the best would be to create a custom Cache Manager which will do all the manipulation with cached concert and use default auto configured cache for all other methods, but I can't get my custom manager to work and didn't find any good example on how to implement a custom CacheManager.

Here is what I have:

Application.java:

@SpringBootApplication
@ComponentScan
@EnableCaching
@EnableScheduling
public class SpringBootWebApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(applicationClass);
    }

    private static Class<SpringBootWebApplication> applicationClass = SpringBootWebApplication.class;


    @Bean(name = "eventsCacheManager")
    public EventsCacheManager eventsCacheManager() {

        return new EventsCacheManager();
    }

    @Primary
    @Bean(name = "cacheManager")
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }

    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
        EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
        cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cmfb.setShared(true);
        return cmfb;
    }


}

EventsCacheManager.java

@Component
public class EventsCacheManager implements CacheManager  {

    @Override
    public Cache getCache(String s) {
        return null;
    }

    @Override
    public Collection<String> getCacheNames() {
        return null;
    }
}

EventsCacheManager.java - how to implement it?

@Component
public class EventsCacheManager implements CacheManager  {

    @Override
    public Cache getCache(String s) {
        return null;
    }

    @Override
    public Collection<String> getCacheNames() {
        return null;
    }
}

I would really appreciate if someone can give me an example on how I should implement my custom CacheManager

Answer

John Blum picture John Blum · Jun 13, 2016

I did not spend much time thinking about your requirements/use case, but I do think a custom CacheManager would work in this situation, assuming the "custom" CacheManager logic is correct.

So, by default, Spring looks for a bean in the context with the name "cacheManager" and uses it for all cached operations. In your configuration, you clearly have 2 "CacheManagers" defined...

@Bean(name = "eventsCacheManager")
public EventsCacheManager eventsCacheManager() {

    return new EventsCacheManager();
}

@Primary
@Bean(name = "cacheManager")
public CacheManager cacheManager() {
    return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}

I.e. the "eventsCacheManager" (custom) and "cacheManager" (standard). Indeed, you even marked the "cacheManager" as primary (using the @Primary annotation). Of course, had you not done that, Spring would certainly have complained since more than 1 bean of a given type (i.e. CacheManager) was found in the context when performing auto-wiring (which defaults to auto-wire by type).

So, in order to call out which cache management strategy (i.e. CacheManager) to use at runtime with a particular service call, you also need to tell Spring which CacheManager (aka strategy) to use, like so...

@Override
@Cacheable(value = "concerts", cacheManager="eventsCacheManager")
public List<Event> getEvents(String eventsForUser, Date startDate, Date endDate) throws Exception {
    return fetchEventsFromTheServer(eventsForUser, startDate, endDate);
}

I.e. using the cacheManager attribute in the @Cacheable annotation to indicate which caching strategy to use.

See Spring's Reference Doc on Custom cache resolution for more details.

Hope this helps get you going.