I'm working with a Spring Boot application that needs to use a cache and using Spring caching framework, and I'm importing a library (that I have little control over) that also has a cache. I'm getting errors that there are multiple CacheManager
implementations so I need to use a CacheResolver
. So far makes perfect sense. Looked at CacheResolver
interface, looks reasonable.
So I have created my own simple CacheResolver
and created it as a bean, but it isn't being picked up automatically - ok, I'm used to spring magic but never mind, so how do I register it?
The only way I have managed to get a custom CacheResolver
recognised by the framework so far is by making my @Configuration
class implement CachingConfigurator
interface and overriding the no method argument cacheResolver()
- however that's frustratingly useless as I need to pass that method my 2x CacheManager
implementations to add it to constructor of my custom CacheResolver
- as soon as I add those beans as method arguments it stops working because, whilst the bean is registered normally, that particular interface method is no longer an override and so it doesn't get called as part of the CachingConfigurator
lifecycle. It just doesn't feel like the intended/idiomatic way of doing things either but I've found no better documentation.
Importantly, I don't want to be putting cacheManager = xyz
or cacheResolver = zyx
in all my Cacheable annotations because, as I say, some of those annotations are in a library I don't own.
Anyone had to handle this situation? Feels like I'm missing something obvious. Thanks!
I would suggest using the Spring Framework ObjectProvider
in your custom CacheResolver
instead of specific or multiple CacheManager
implementations. This way you are flexible and it really doesn't matter how many CacheManager
instances there are.
public class CustomCacheResolver implements CacheResolver {
private final ObjectProvider<CacheManager> managers;
public CustomCacheResolver(ObjectProvider<CacheManager> managers) {
this.managers = managers;
}
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
var caches = new ArrayList();
for (var cacheName : context.getOperation().getCacheNames()) {
var cache = resolveCache(cacheName);
if (cache == null) {
throw new IllegalArgumentException("Cannot find cache named '" +
cacheName + "' for " + context.getOperation());
}
caches.add(cache);
}
return caches;
}
private Cache resolveCache(String cacheName) {
managers.stream().map((cm) -> cm.getCache(cacheName)).filter(Object::nonNull).findFirst().orElse(null);
}
}
Something along these lines. This way it doesn't matter if there is 1 or 100 cachemanagers. They will be lazily resolved when the resolveCaches
method is being called.
The configuration can be adapted to accommodate this.
@Configuration
public MyCacheConfiguration implements CachingConfigurer {
private final ObjectProvider<CacheManager> managers;
public MyCacheConfiguration(ObjectProvider<CacheManager> managers) {
this.managers = managers;
}
@Override
public CacheResolver cacheResolver() {
return new CustomCacheResolver(managers);
}
}
The benefit of using an ObjectProvider
over a List
is that with the List
the CacheManager
instances will be eagerly resolved for injection. This might then suddenly be earlier then it otherwise would be. With the ObjectProvider
this is delayed until the moment they are actually needed.