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) {
...
}
...
}
Related
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)
I'm trying to write a multi-tenant Spring Boot application but having trouble to eager initialize beans when the server starts (i.e. not lazily once the tenant requests the bean)
To support multi-tenancy, i created a #CustomerScoped annotation that creates objects based on a ThreadLocal String value.
My configuration provides a bean like this and lazily initializes it:
#Autowired
private AutowireCapableBeanFactory beanFactory;
#Bean
#CustomerScoped
public Scheduler getScheduler() {
CreateDefaults job = factory.createBean(CreateDefaults.class));
Scheduler scheduler = new Scheduler();
scheduler.schedule(job);
return scheduler;
}
#PostConstruct
public void init() {
CustomerScope.setCustomer("tenant1");
getScheduler();
CustomerScope.setCustomer("tenant2");
getScheduler();
CustomerScope.clearCustomer();
}
When starting the server, two Schedulers should be created, each of which would execute their own instance of "Create Defaults".
When tenants access the application themselves, they should be getting their own instance of this Scheduler.
This seems to work but i wonder whether this is the correct way of doing things.
In particular, i am worried about the fact that the beanFactory isn't scoped itself.
Would this approach work and scale for more complex systems?
My code sample was actually correct.
The Beanfactory doesn't need to be scoped itself, it just has to be made aware of the scope, which in my case can be achieved in the configuration:
#Bean
public static CustomScopeConfigurer customScope() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope(CustomerScope.CUSTOMER_SCOPE_NAME, new CustomerScope());
return configurer;
}
The new #RefreshScope in Spring Cloud is great. But a side effect is that beans become lazily loaded. Most of the time this is a non-issue, but eagerly loaded beans allow for DI, property value setting, etc. to be assured at startup time rather than later at runtime. Is there any way to employ #RefreshScope AND cause the affected beans to be eagerly loaded?
I solved this "problem" by implementing a listener that catches the Refresh event.
Then I iterate over every bean in the context and call the getClass() method. (Any other method call on the bean will trigger its instantiation - getClass() is convenient since it exists for all beans.)
#Service
public class RefreshListener {
private final Logger logger = LoggerFactory.getLogger(RefreshListener.class);
#Autowired
ApplicationContext applicationContext;
#EventListener
public void onRefreshScopeRefreshed(final RefreshScopeRefreshedEvent event) {
logger.info("Received Refresh event. Refreshing all beans...");
for (String beanName : applicationContext.getBeanDefinitionNames()) {
logger.info("Refreshing bean " + beanName);
applicationContext.getBean(beanName).getClass();
}
}
}
Hope it helps.
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.