Using EhCache in Spring 4 without XML
Asked Answered
S

2

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
Stocker answered 21/2, 2014 at 20:9 Comment(1)
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 github.com/spring-projects/spring-framework/tree/master/….Zacatecas
S
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();
    }
}
Stocker answered 21/2, 2014 at 20:32 Comment(10)
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.Zabrze
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?Jaan
@KevinM you could use Spring Profile for this as well. Using this will start one or the other based on the active profile. docs.spring.io/spring/docs/current/javadoc-api/org/…Stocker
Oh right, so I can annotate the method with the profile?Jaan
@KevinM I would split them into two separate config classes and annotate eachStocker
You also need to register a shutdown hook in Spring to cause the bean to be destroyed as per the annotation, as I've recently discovered.Evertor
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 :)Clothespress
@Erich, This config not working for me. java.lang.IllegalArgumentException: CacheManager must not be null how fixed it?Ventral
@Ventral had the same problem. I fixed it replacing return new SimpleCacheResolver(); with return new SimpleCacheResolver(cacheManager()); in the CachingConfig classCrosslet
@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.Wendiewendin
A
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();
    }
}
Arcturus answered 24/2, 2014 at 15:59 Comment(4)
+1 like the idea of abstraction. Will play with thisStocker
I needed it to combine the two cache managers; it may be overkill for your needs, but removing the composite should be straightforward.Arcturus
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?Purpura
@Purpura docs.spring.io/spring-framework/docs/current/javadoc-api/org/…, implements org.springframework.cache.CacheManagerArcturus

© 2022 - 2024 — McMap. All rights reserved.