Disable spring method caching through external property - java

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>

Related

is it possible to have Spring configuration relying only on annotations apart from being spring boot application?

I'm new in Spring applications, and see the big difference between configurations in springBoot and spring. So my questin is: apart from spring-boot, is there a way to setup a proper spring application(with web mvc, security, aop, ...), without any xml config file (ie : config relying only on annotations).
Yes, there is a way to do this in Spring. Spring Boot is after all an enhanced, autoconfigured Spring (with other cool features). That means that everything there is in Spring Boot should be achievable in Spring as well, but you would have do a bit/a lot of Your own extra work.
Moving straight to the point, in order to achieve what you want, you would need to undertake the following steps:
Create a class, which will store all the configuration (basically the properties you would store in the xml file) - let's call it AppConfig.class
Annotate the AppConfig.class with #Configuration - this will inform Spring that this class is the source of configuration;
Annotate the AppConfig.class with #ComponentScan("com.app") - here, You need to provide a package, from which Spring has to start component scanning in order to find Beans to be registered in Spring Container. Important note is, that it will scan the package and it's subpackages, so you would mostly want to provide here the top level package;
If you need some data to be injected into your beans, you would want to use the #PropertySource("classpath:application.properties") - I have provided here the default value, which Spring Boot uses internally in case you want to inject some data into your beans at runtime. For this to work, you need to inject into AppConfig.class an Environment.class
To show it on the example:
#Configuration
#ComponentScan("com.app")
#PropertySource("classpath:application.properties")
public class AppConfig {
// it will help to pull the properties incorporated in the file you have provided in the #PropertySource annotation
private Environment environment;
//inject it
public AppConfig(Environment environment) {
this.environment = environment;
}
// build your beans - the getProperty method accepts the key from application.properties
// file and return a value as a String. You can provide additional arguments to convert
//the value and a default value if the property is not found
#Bean
public Product product() {
return new Product(
environment.getProperty("product.name", "XXX"),
environment.getProperty("product.price", BigDecimal.class, BigDecimal.ZERO),
environment.getProperty("product.quantity", Integer.class, 10)
);
}
}
I hope that it helps

Custom `RelyingPartyRegistrationRepository` implementation

It looks like Spring always uses InMemoryRelyingPartyRegistrationRepository to return a RelyingPartyRegistrationRepository typed bean, refer to https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java.
Question: how can I inject (autowire) my own implementation of RelyingPartyRegistrationRepository? Say I would like to allow the auto wired relying party repository auto reload from database once I have SAML configuration for a certain customer updated. Is this doable?
You can provide your own bean and spring boot auto configuration will back off.
#Configuration
#EnableConfigurationProperties(Saml2RelyingPartyProperties.class)
public class SamlConfig{
#Bean
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(Saml2RelyingPartyProperties properties) {
-- Provide custom repository implementation
}
}
You may need other changes after you create your own bean based on what you need.

Spring Boot: Overriding CacheManager bean makes cache related properties not work

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)

spring bean initialization depends on variable number of other beans

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) {
...
}
...
}

How to read properties (or any other text) file in Java Spring MVC app?

I need to read java properties file inside my Spring MVC app but I can't find the way to do that. I tried several answers from similar question here on SO, but I was unsuccessful. I'm new to Java, and especially Spring MVC so I probably messed up something.
I'm not sure anymore that the file is being successfully deployed. I'm using Tomcat btw.
If you are using Spring 3.1+ you can use the #PropertySource annotation:
#Configuration
#PropertySource("classpath:/com/example/app.properties")
public class AppConfig {
// create beans
}
or for XML-based configuration you can use the <context:property-placeholder>:
<beans>
<context:property-placeholder location="classpath:com/example/app.properties"/>
<!-- bean declarations -->
</beans>
then you can autowire the key in the properties file using the #Value annotation:
#Value("${property.key}") String propertyValue;
Read more details in the Spring reference docs.
You can have properties files automatically loaded in Spring by using the PropertySourcesPlaceholderConfigurer.
Here is an example of configuring a PropertySourcesPlaceholderConfigurer using Spring JavaConfig:
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer props = new PropertySourcesPlaceholderConfigurer();
props.setLocations(new Resource[] {
new ClassPathResource("/config/myconfig.properties"),
new ClassPathResource("version.properties")
});
}
This will load the properties from the files above on the classpath.
You can use these properties in property replacements within your application. For example, assume that there is a property in one of those files above named myprop. You could inject myprop's value into a field using the following:
#Value(${myprop})
private String someProperty;
You can also access the values of the properties by injecting Spring's Environment object into your classes.
#Resource
private Environment environment;
public void doSomething() {
String myPropValue = environment.getProperty("myprop");
}
In order to read any old file from within a web application the link that Frederic posted in the comments above provides a good explanation of the normal classloader hurdles one encounters when attempting to read files from within their war file and the solutions around it.
You can try the below code.
Add this to servelt-context.xml
<context:property-placeholder location="classpath:config.properties"/>
And to access the contents of config file in java,
#Value("${KEY}")
private String value;

Categories

Resources