40

Is there a way to initialize EhCache without xml in either Spring 4 or with Spring Boot?

I noticed Spring Boot 1.0.0.RC3 doesn't have any ehcache dependencies but the Spring 4.0GA release post mentioned it has improved support for EhCache. Also, Spring 3 had the class org.springframework.cache.ehcache.EhCacheCacheManager but that's no longer part of the dependencies.

Edit: Spring 4 does have EhCache support. You must add the dependency:

<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>

Edit2: I've tried the following and I think I'm close but I'm getting an error:

@Bean
@Override
public CacheManager cacheManager() {
    CacheConfiguration cacheConfiguration = new CacheConfiguration();
    cacheConfiguration.setName("primary");
    cacheConfiguration.setMemoryStoreEvictionPolicy("LRU");
    cacheConfiguration.setMaxEntriesLocalHeap(0);

    net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
    config.addCache(cacheConfiguration);

    net.sf.ehcache.CacheManager cacheManager = new net.sf.ehcache.CacheManager(config);
    cacheManager.setName("EhCache");

    return new EhCacheCacheManager(cacheManager);
}

@Bean
public EhCacheManagerFactoryBean factoryBean() {
    return new EhCacheManagerFactoryBean();
}

Error

Caused by: net.sf.ehcache.CacheException: Another unnamed CacheManager already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.
The source of the existing CacheManager is: [Programmatically configured]
    at net.sf.ehcache.CacheManager.assertNoCacheManagerExistsWithSameName(CacheManager.java:590)
    at net.sf.ehcache.CacheManager.init(CacheManager.java:384)
    at net.sf.ehcache.CacheManager.<init>(CacheManager.java:263)
    at org.springframework.cache.ehcache.EhCacheManagerFactoryBean.afterPropertiesSet(EhCacheManagerFactoryBean.java:166)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549)
    ... 15 more
Erich
  • 2,743
  • 1
  • 24
  • 28
  • Spring4 still has that class, so not sure where you have the idea that it isn't working anymore. It is in the `spring-context-support` jar. See https://github.com/spring-projects/spring-framework/tree/master/spring-context-support/src/main/java/org/springframework/cache/ehcache. – M. Deinum Feb 23 '14 at 13:24

2 Answers2

52

XML-less configuration of EhCache in Spring

@Configuration
@EnableCaching
public class CachingConfig implements CachingConfigurer {
    @Bean(destroyMethod="shutdown")
    public net.sf.ehcache.CacheManager ehCacheManager() {
        CacheConfiguration cacheConfiguration = new CacheConfiguration();
        cacheConfiguration.setName("myCacheName");
        cacheConfiguration.setMemoryStoreEvictionPolicy("LRU");
        cacheConfiguration.setMaxEntriesLocalHeap(1000);

        net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
        config.addCache(cacheConfiguration);

        return net.sf.ehcache.CacheManager.newInstance(config);
    }

    @Bean
    @Override
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheManager());
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

    @Bean
    @Override
    public CacheResolver cacheResolver()    {
        return new SimpleCacheResolver();
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
         return new SimpleCacheErrorHandler();
    }
}

Alternatively for testing, you can use a simple ConcurrentMapCache running without XML as below.

@Configuration
@EnableCaching
public class CachingConfig implements CachingConfigurer {
    @Bean
    @Override
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();

        List<Cache> caches = new ArrayList<Cache>();
        caches.add(new ConcurrentMapCache("myCacheName"));
        cacheManager.setCaches(caches);

        return cacheManager;
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

    @Bean
    @Override
    public CacheResolver cacheResolver()    {
        return new SimpleCacheResolver();
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
         return new SimpleCacheErrorHandler();
    }
}

Edit: Updated to add shutdown method on underlying cache

Edit: Added configuration for error handler and cache resolver

Edit: changing constructor call on SimpleCacheResolver which resolves CacheManager must not be null issue (Spring v5.1+)

@Configuration
@EnableCaching
public class CachingConfig implements CachingConfigurer {

    public static final String USER_CACHE_INSTANCE = "my-spring-ehcache";
    private final CacheManager cacheManager;
    private final net.sf.ehcache.CacheManager ehCacheManager;


    public CachingConfig() {
        CacheConfiguration cacheConfiguration = new CacheConfiguration();
        cacheConfiguration.setName(USER_CACHE_INSTANCE);
        cacheConfiguration.setMemoryStoreEvictionPolicy("LRU");
        cacheConfiguration.setMaxEntriesLocalHeap(1000);
        net.sf.ehcache.config.Configuration config
                = new net.sf.ehcache.config.Configuration();
        config.addCache(cacheConfiguration);
        this.ehCacheManager = net.sf.ehcache.CacheManager.newInstance(config);
        this.cacheManager = new EhCacheCacheManager(ehCacheManager);
    }

    @Bean(destroyMethod="shutdown")
    public net.sf.ehcache.CacheManager ehCacheManager() {
        return ehCacheManager;
    }

    @Bean
    @Override
    public CacheManager cacheManager() {
        return cacheManager;
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager);
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        return new SimpleCacheErrorHandler();
    }
}
Adam
  • 5,215
  • 5
  • 51
  • 90
Erich
  • 2,743
  • 1
  • 24
  • 28
  • The original java config example did not show that the net.sf.ehcache.CacheManager should also be declared as a bean, so that its shutdown() method is called when the bean is destroyed. – Michael R Apr 10 '14 at 17:53
  • How can you switch between the "test" one using ConcurrentMapCache and the production level one using ehCache? Right now, I have an application.properties and an application-unittest.properties files I use to switch between a MYsql db and an in memory db for testing. How can you do the same thing with the cache? – Kevin M Oct 07 '14 at 18:35
  • 1
    @KevinM you could use Spring Profile for this as well. Using this will start one or the other based on the active profile. http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html – Erich Oct 07 '14 at 18:58
  • Oh right, so I can annotate the method with the profile? – Kevin M Oct 07 '14 at 19:02
  • 1
    @KevinM I would split them into two separate config classes and annotate each – Erich Oct 14 '14 at 11:13
  • You also need to [register a shutdown hook](http://stackoverflow.com/a/32963252/2495717) in Spring to cause the bean to be destroyed as per the annotation, as I've recently discovered. – ben3000 Oct 06 '15 at 06:37
  • 1
    Spring 4.2.5 `org.springframework.cache.annotation.CachingConfigurer` requires two additional methods to be implemented for the EHCache 3.x: `cacheResolver` and `errorHandler`. Would be perfect if those would be covered in the answer :) – J.Olufsen May 19 '16 at 06:02
  • @Erich, This config not working for me. `java.lang.IllegalArgumentException: CacheManager must not be null` how fixed it? – Hadi J Jul 25 '17 at 12:18
  • 4
    @Hadi had the same problem. I fixed it replacing `return new SimpleCacheResolver();` with `return new SimpleCacheResolver(cacheManager());` in the CachingConfig class – Santiago Tórtora Aug 04 '17 at 19:58
  • 1
    @SantiagoTórtora when you call `cacheManager()` you will instantiate a new one every time, e.g. Spring will call it once to populate the IoC bean context once when it creates the `CacheManager` bean, and then again when creating the `SimpleCacheResolver` bean. I'll add my version where I create the `cacheManager` in the constructor and keep it as a member variable of the `CacheConfig`, which is fine where Spring is creating it as a singleton. – Adam Mar 18 '19 at 09:26
23

I do this at two levels of abstraction, a configuration file per technology (Ehcache, Redis, etc.) and a general configuration file.

Here's the one for Ehcache (Redis would be similar):

@Configuration
public class EhCacheConfiguration {

    @Bean
    public EhCacheCacheManager ehCacheCacheManager() {

        return new EhCacheCacheManager(ehCacheManagerFactoryBean().getObject());
    }


    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {

        EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();

        cacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cacheManagerFactoryBean.setShared(true);

        return cacheManagerFactoryBean;
    }
}

And here's the general one (complete with Redis hooks):

@Configuration
@EnableCaching
public class CachingConfiguration implements CachingConfigurer {

    @Qualifier("ehCacheCacheManager")
    @Autowired(required = false)
    private CacheManager ehCacheCacheManager;

    @Qualifier("redisCacheManager")
    @Autowired(required = false)
    private CacheManager redisCacheManager;


    @Bean
    @Override
    public CacheManager cacheManager() {

        List<CacheManager> cacheManagers = Lists.newArrayList();

        if (this.ehCacheCacheManager != null) {
            cacheManagers.add(this.ehCacheCacheManager);
        }

        if (this.redisCacheManager != null) {
            cacheManagers.add(this.redisCacheManager);
        }

        CompositeCacheManager cacheManager = new CompositeCacheManager();

        cacheManager.setCacheManagers(cacheManagers);
        cacheManager.setFallbackToNoOpCache(false);

        return cacheManager;
    }


    @Bean
    @Override
    public KeyGenerator keyGenerator() {

        return new DefaultKeyGenerator();
    }
}
Emerson Farrugia
  • 11,153
  • 5
  • 43
  • 51
  • 1
    +1 like the idea of abstraction. Will play with this – Erich Feb 24 '14 at 17:34
  • I needed it to combine the two cache managers; it may be overkill for your needs, but removing the composite should be straightforward. – Emerson Farrugia Feb 24 '14 at 19:15
  • I also like the idea, but I'm getting confused between the CacheManager types. How can you put instances of org.springframework.cache.CacheManager and net.sf.ehcache.CacheManager in the same list? – Gabriel Jul 15 '21 at 15:49
  • @Gabriel https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/ehcache/EhCacheCacheManager.html, implements org.springframework.cache.CacheManager – Emerson Farrugia Jul 16 '21 at 08:30