I have a Spring Boot 2 application with Redis cache. It worked just fine until I overridden CacheManager bean.
Problem: The following configuration property gets ignored (I can't turn off caching anymore):
spring.cache.type=none
Although according to the documentation it should work.
Question: How to make the spring.cache.type=none work?
There is a workaround like this, but it is far from being a good solution.
More details: Here is how my configuration looks like:
#Configuration
public class CacheConfiguration {
#Bean
RedisCacheWriter redisCacheWriter(RedisConnectionFactory connectionFactory) {
return RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
}
#Bean
CacheManager cacheManager(RedisCacheWriter redisCacheWriter) {
Map<String, RedisCacheConfiguration> ttlConfiguration = ...
RedisCacheConfiguration defaultTtlConfiguration = ...
return new RedisCacheManager(
redisCacheWriter, defaultTtlConfiguration, ttlConfiguration
);
}
}
Because you are creating the CacheManager yourself you also have to check spring.cache.type if you want to turn it of.
#Bean
#ConditionalOnExpression("${spring.cache.type} != 'none'")
CacheManager cacheManager(RedisCacheWriter redisCacheWriter) {
A Built in Spring Redis Cache configuration resides in org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
It has a #Conditional(CacheCondition.class) on it.
This CacheCondition checks the value of spring.cache.type property. If its set to "NONE" the whole configuration, including the RedisCacheManager bean won't load at all.
Now as you've created your own configuration where you define the cacheManager by yourself it gets loaded regardless the value of spring.cache.type variable
So you should probably put some conditional value (that will read the spring.cache.type value or your custom condition)
Related
I have a Spring (not Boot) application with a successfully configured and used Caching mechanism through the CacheManager that looks like the following:
#Configuration
#EnableCaching(proxyTargetClass=true)
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = List.of(
new ConcurrentMapCache("cache_name_1"),
// ....
);
cacheManager.setCaches(caches);
return cacheManager;
}
}
Spring also supports ETags as can be read here. And it could be configured as follows:
#Bean
public ShallowEtagHeaderFilter shallowEtagHeaderFilter() {
return new ShallowEtagHeaderFilter();
}
Is there any need for ETags when one has already configured Caching used the first approach above?
You have 2 different concepts.
Server side cache: Calls from different clients with the same parameters can get the same result from server cache.
Client side cache: Get once and use ETag to see if anything has changed, no need to get it again if not modified.
I am trying to start a springboot application where this annotation has been used. When I try to start the application it gives me the following error:
org.springframework.boot.autoconfigure.condition.OnBeanCondition$BeanTypeDeductionException Failed to deduce bean type for com.shutterfly.sbs.platform.SbsPlatformConfigurationClientConfig.getRestTemplate
Code:
#ConditionalOnMissingBean
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
The #ConditionalOnMissingBean annotation is used to load a bean only if a given bean is missing:
#Bean
#ConditionalOnMissingBean(SomeBean.class)
public SomeBean otherBean(){
return new SomeBean();
}
The above bean will get loaded by Spring only if there is no other bean of this type present in the context. On the other hand, if there is already a bean of the type SomeBean present in the application context, the above bean will not be created.
Some use cases where this annotation comes in handy are:
Specifying a fallback bean which gets only loaded as a backup if there is no bean of the same type present (for example: using an in-memory database if there is no real database configured)
Specifying a default bean which allows being overridden in the case that a more specific bean of the same type is present in the context (for example: using a default authentication mechanism unless someone decides to replace it with his own custom authentication)
Reference: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html
The #ConditionalOnMissingBean annotation is a spring conditional annotation for registering beans only when they are not already in the application context.
See the documentation: https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.html
We use #ConditionalOnMissingBean if we want to include a bean only if a specified bean is not present. For ex.
Let's configure a transactionManager bean that will only be loaded if a bean of type JpaTransactionManager is not already defined:
#Bean
#ConditionalOnMissingBean(type = "JpaTransactionManager")
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
To understand more consider this scenario as well.
Let's say in my project I configured a bean videoDecoderService
#Bean
#ConditionalOnMissingBean(VideoDecoderService.class)
public videoDecoderService videoDecoderService(){
return new VideoDecoderService;
}
What it will do is whoever is using my project would be able to override the videoDecoderService with the videoDecoderService of their own. If they are not writing their own videoDecoderService then this default one will be provided.
You should not skip the part of the documentation that clearly says:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only.
It goes on later to say:
If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after
In rather simple words it might mean that the #Bean brought in by the some other auto-configuration should take precedence over the one that you defined. For that to work, you need to properly set-up the order of those configurations via #AutoConfigureBefore.
In my Spring/Grails/Groovy app, I configure some cache beans:
rulesCache(InMemoryCache){..}
countriesCache(InMemoryCache){..}
myService(ServiceBean){
cache = ref('rulesCache')
}
A cache manager provides specialized services when retrieving caches, so I give the manager a list of cache beans:
cacheMgr(CacheManager){
caches = [ ref('rulesCache'), ref('countriesCache')]
}
Services must get cache beans from the manager, they can't be "wired in" (the manager returns a cache delegate, not the cache itself, that's why), so I got around this problem by doing:
cacheMgr(CacheManager){
caches = [ ref('rulesCache'), ref('countriesCache')]
}
cacheMgrDelegate(MethodInvokingFactoryBean) { bean ->
bean.dependsOn = ['cacheMgr']
targetObject = cacheMgr
targetMethod = 'getManager'
}
myService(SomeService){
cache = "#{cacheMgrDelegate.getCache('rulesCache')}"
}
This works fine, but cache beans are arbitrary, so I can't provide a list to the manager. I managed to get around this problem by listening for post initialization events from cache type objects, and registering each cache manually with the manager:
CacheManager implements BeanPostProcessor {
postProcessAfterInitialization(bean, beanName){
if(bean instanceof ICache)
registerCache(bean)
return bean
}
}
Problem
The issue is that Spring is doing initialization on myService before cacheManager registers all cache beans, so getCache() returns null:
myService(SomeService){
cache = "#{cacheMgrDelegate.getCache('rulesCache')}"
}
I understand why it's happening. I can't use dependsOn since cache beans are arbitrary, and this is where I'm stuck.
Possible Solution
During spring config phase, CacheManager.getCache(name) could return a lightweight "proxy"-like object while saving a reference to each proxy generated:
getCache(String name){
CacheProxy proxy = createProxy()
proxies.add(proxy)
return proxy
}
After all beans are configured and app context is set, cacheManager simply iterates the list of proxies and completes initialization:
onApplicationContext(){
proxies.each{ proxy ->
completeInit(proxy)
}
}
Is there a better option? I'm out of ideas :-)
Can't you simply autowire all instances of ICache instead? It should create necessary dependencies between CacheManager and the caches:
CacheManager {
#Autowired
public void setCaches(List<ICache> caches) {
...
}
...
}
I configured spring method caching with ehcache and annotation driven configuration.
I would like however to be able to disable it from a configuration file we use in the application.
My first idea was to call net.sf.ehcache.CacheManager.CacheManager() with no arguments if method caching is disabled. This throws exception:
java.lang.IllegalArgumentException: loadCaches must not return an empty Collection
at org.springframework.util.Assert.notEmpty(Assert.java:268)
at org.springframework.cache.support.AbstractCacheManager.afterPropertiesSet(AbstractCacheManager.java:49)
My second idea was to configure the net.sf.ehcache.CacheManager.CacheManager() with default data so that the cache is not used (maxElementsInMemory 0 etc.). But then the cache is still used, which is not what I want.
There is a property net.sf.ehcache.disabled but I do not want do disable hibernate caching that also uses ehcache.
Q How can I configure everything to have spring method caching but disable it from my external configuration file? I do not want to modify the application-context nor the code to enable/disable method caching. Only the configuration file we use in the application can be modified.
What I was looking for was NoOpCacheManager:
To make it work I switched from xml bean creation to a factory
I did something as follows:
#Bean
public CacheManager cacheManager() {
final CacheManager cacheManager;
if (this.methodCacheManager != null) {
final EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
ehCacheCacheManager.setCacheManager(this.methodCacheManager);
cacheManager = ehCacheCacheManager;
} else {
cacheManager = new NoOpCacheManager();
}
return cacheManager;
}
You can use a spring profile, to enable (or not) the spring caching support
<beans profile="withCache">
<cache:annotation-driven />
</beans>
I am using java annotation based configuration for initializing ehcache based caching, with Spring 3.1.
Here is the sample code...
#Configuration
#EnableCaching
public class EhcacheConfig implements CachingConfigurer {
.....
#Bean
public CacheManager cacheManager() {
.....
EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
bean.setCacheManagerName(CACHE_MANAGER);
bean.setShared(Boolean.TRUE);
File file = new File(property + Constants.Slash + EHCACHE_XML);
bean.setConfigLocation(new FileSystemResource(file));
try {
bean.afterPropertiesSet();
} catch (Exception e) {
throw new RuntimeException(e);
}
EhCacheCacheManager cm = new EhCacheCacheManager();
cm.setCacheManager(bean.getObject());
return cm;
}
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}
There is a valid ehcache.xml with 1 cache declared in it.
This is all the configuration that I have for initializing ehcache with Spring. There is no XML based initialization in the application.
At runtime, I have noticed that cacheManager() is initialized, as expected. After its successful execution, the code fails to complete the initialization by erring out in:
CachingInterceptor.afterPropertiesSet() ->
if (this.cacheManager == null) {
throw new IllegalStateException("'cacheManager' is required");
}
I have done some investigation.
It appears that the problem occurs when CachingInterceptor is being initialized by ProxyCachingConfiguration.
ProxyCachingConfiguration is derived from AbstractCachingConfiguration.
AbstractCachingConfiguration has a method called:
#PostConstruct
protected void reconcileCacheManager()
This method is not invoked. Had it been invoked, the cacheManager instantiated in EhcacheConfig.cacheManger() would have been setup correctly for used by the CacheInterceptor.afterPropertiesSet().
I do not understand the reason why reconcileCacheManager() is not invoked before CacheInterceptor.afterPropertiesSet() is invoked.
Am I missing something? Can some one help me with the problem that I am facing?
Thank you.
First, you might consider extracting the initialization of the EhCacheManagerFactoryBean to its own #Bean method.
This way you can simply instantiate, configure, and return the FactoryBean without having to invoke afterPropertiesSet() yourself. This ensures that the object is a properly-managed Spring bean and that it can receive any other callbacks in might register for (like DisposableBean#destroy()) in this particular case.
Assuming that new #Bean method is named "ecmfb", you can simply call ecmfb().getObject() from within your cacheManager() method, and you'll be guaranteed at that point that the FactoryBean contract (i.e. afterPropertiesSet()) has been satisfied.
Second, you might care to know that your #Bean methods can throw any exception you like. So for example if you did not choose to extract the FactoryBean as I suggest above, you could still simplify the situation by declaring a 'throws Exception' clause on your cacheManager #Bean method. This will save you the current try/catch noise.
Finally, to address why the #PostConstruct method is not being called, let me ask how you're bootstrapping the Spring container. If you're working with AnnotationConfig(Web)ApplicationContext, the CommonAnnotationBeanPostProcessor should be registered by default. The same is true if you're using or . CABPP is responsible for the detection and processing of annotations like #PostConstruct, #PreDestroy and others. Please provide a bit more information about your bootstrapping approach and we'll go from there.