The Problem
In most cases with a Spring Boot app, we likely only ever have one cache provider. Spring Boot provides quite a few cache providers out of the box. But what happens if we want to use multiple providers in our application? We explore a solution to this problem in today’s post.
Note: This post assumes working knowledge of spring boot and spring caching
Setting Things Up
The first thing we do is setup Ehcache using JCache. To do this let’s add a few dependencies to the build file for spring caching, ehcache, and the java cache api:
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.ehcache:ehcache:3.8.0")
implementation("javax.cache:cache-api")
Next, we let spring know where to find our jcache config in the application.yml:
spring:
cache:
jcache:
config: classpath:ehcache.xml
Lastly, we enable caching and setup a small class that uses our configured cache:
@Configuration
@EnableCaching
class CacheConfig
// Found in BookService.kt
@Cacheable(cacheNames = ["books"])
fun getBooks(author: String) = books.getOrDefault(author, emptyList())
Now that we’ve got ehcache configured, let’s set up redis. Like before, we start off by modifying the dependencies:
implementation("org.springframework.boot:spring-boot-starter-data-redis")
By including this starter, Spring Boot configures all of the necessary components in order to use redis. With our application in this state, if we bootRun
and check out the auto-configuration report (http://localhost:8080/actuator/conditions) we see something like this:
{
"RedisCacheConfiguration": {
"notMatched": [
{
"condition": "OnBeanCondition",
"message": "@ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) found beans of type 'org.springframework.cache.CacheManager' cacheManager"
}
]
}
}
Unfortunately, this means that Spring Boot won’t be autoconfiguring our RedisCacheManager bean for us since it already sees a Bean of type CacheManager
from the Ehcache/JCache configuration.
The Solution
So how do we fix this? Well this is one of those cases where we need to take over the configuration from Spring Boot. To do this we create a more complex CacheConfig
class that declares two CacheManager
Beans:
@Bean
@Primary
override fun cacheManager(): CacheManager =
RedisCacheManager.builder(redisConnectionFactory).build()
@Bean
fun jCacheCacheManager(): JCacheCacheManager {
val cachingProvider = Caching.getCachingProvider()
val configLocation = cacheProperties().resolveConfigLocation(cacheProperties().jcache.config)
val cacheManager = cachingProvider.getCacheManager(configLocation.uri, classLoader)
jCacheManagerCustomizers.orderedStream().forEach { it.customize(cacheManager) }
return JCacheCacheManager(cacheManager)
}
Now that we have the two cache managers defined, let’s update the BookService
to include a new cached method:
@Cacheable(cacheNames = ["books"], cacheManager = "jCacheCacheManager")
fun getBooks(author: String) = books.getOrDefault(author, emptyList())
@Cacheable(cacheNames = ["authors"], key = "'all-authors'")
fun getAuthors() = listOf(books.keys.toTypedArray())
Notice that getBooks
specifies its CacheManager
while getAuthors
does not. This means that getBooks
uses the jCacheCacheManager
Bean while getAuthors
uses the Primary CacheManager
Bean which we defined to be a RedisCacheManager
. We can see this in action in the tests. And there we have it, a working Spring Boot app with two cache providers!
Things to Look Out For
- If we’re migrating from previously having only one provider to multiple providers, then we may have specified the application property
spring.cache.type
in order to force the usage of a specific cache type. When combining multiple providers, we want to avoid that. - We’re now owning the configuration of our
CacheManager
Beans, so this means some of the behavior might differ from the vanilla autoconfiguration. We especially want to be careful if we rely on Customizers because we now need to manually apply those. - We want to make sure that whichever provider we would like to have as the default is the Primary
CacheManager
Bean to save ourselves the effort of having to constantly specify the manager. This way various cached items need to opt into the secondary manager.
Check out the example code on github to see the complete example!
Thanks for reading!