I had a method that had been cached before, but stopped working after being refactored. The method cached application properties that were being read from a webdav repository. I noticed while debugging some other production code that the properties file was being read on every request.
I verified the following:
- The class was an annotated spring bean
@Repository
- The method was annotated as a cacheable method
@Cacheable(“GlobalPropertiesDao.getProperties”)
- The method was public
- The package was being scanned for components
<context:component-scan base-package=”…”/>)
- The spring context was configured for annotated cache beans
<cache:annotation-driven/>
- Ehcache was configured for caching
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true"/>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="ehcache"/>
- Verified that there was an ehcache.xml that was being placed in the root of my classpath
- Verified that the specified name of my cache was in the ehcache.xml
<cache name="GlobalPropertiesDao.getProperties" maxElementsInMemory="1000" eternal="false" overflowToDisk="false"
timeToIdleSeconds="300" timeToLiveSeconds="300"/>
Still the method was not being cached. While reading through the Spring documentation, I ran across the following paragraph.
Note
In proxy mode (which is the default), only external method calls coming in through the proxy
are intercepted. This means that self-invocation, in effect, a method within the target object
calling another method of the target object, will not lead to an actual caching at runtime even
if the invoked method is marked with @Cacheable – considering using the aspectj mode in
this case.
Doh!!!! That was it. I provide a method that looks for the property within the properties and it automatically looks for the property that is defined for this environment (property.env=…) and the property without an environment (property=…). The class was composed of 2 methods. One that looks for the property and one that reads the property file. I chose to cache the reading of the properties file so that the webdav would take the file read hit at most once every 5 minutes. Had I chosen to cache the individual properties, the webdav could take the file read hit once per property every 5 minutes.
I started with the following class:
package com.appriss.jxp.properties;
import com.appriss.justicexchange.commons.http.HttpUtils;
import com.appriss.justicexchange.commons.utility.Utility;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Properties;
import static com.appriss.core.Environment.*;
/**
* JX Property Dao.
* @author jmiller Jan 19, 2012 12:58:26 PM
* @author Norris Shelton
*/
@Repository
public class PropertiesDao {
private static final Logger log = LoggerFactory.getLogger(PropertiesDao.class);
/**
* Gets a JX Portal property. It first tries to find the property with the environment as the suffix. If not
* found, it will look for the property without the environment.
* @param key the key of the property
* @return the value of the property
* @throws Exception if the property is not found
*/
public String getProperty(String key) throws IOException {
String property = getProperties().getProperty(key + '.' + getEnvironment().toString());
if (StringUtils.isBlank(property)) {
property = getProperties().getProperty(key);
if (StringUtils.isBlank(property)) {
throw new IOException("Global property not found " + key + '.' + getEnvironment().toString() + " or " + key);
}
}
return property;
}
/**
* Reads the properties from the webdav for this environment.
* @return Properties object
*/
@Cacheable("GlobalPropertiesDao.getProperties")
public Properties getProperties() throws IOException {
Properties properties = new Properties();
String response = HttpUtils.getResponse(Utility.getWebdavServer() + "/product/jxportal/config/jxportal.properties");
if (StringUtils.isNotBlank(response)) {
//noinspection ConstantConditions
properties.load(new ByteArrayInputStream(response.getBytes()));
}
return properties;
}
}
The getProperties method on line 54 was being called by the getProperty method on line 41. That was my problem. I ended up splitting up the code into 2 classes. Here are the 2 classes.
package com.appriss.jxp.properties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import static com.appriss.core.Environment.*;
/**
* JX Property Service class.
* @author Norris Shelton
*/
@Component
public class PropertiesService {
@Autowired
private PropertiesDao propertiesDao;
/**
* Gets a JX Portal property. It first tries to find the property with the environment as the suffix. If not
* found, it will look for the property without the environment.
* @param key the key of the property
* @return the value of the property
* @throws IOException if the property is not found
*/
public String getProperty(String key) throws IOException {
String property = propertiesDao.getProperties().getProperty(key + '.' + getEnvironment().toString());
if (StringUtils.isBlank(property)) {
property = propertiesDao.getProperties().getProperty(key);
if (StringUtils.isBlank(property)) {
throw new IOException("Global property not found " + key + '.' + getEnvironment().toString() + " or " + key);
}
}
return property;
}
}
package com.appriss.jxp.properties;
import com.appriss.justicexchange.commons.http.HttpUtils;
import com.appriss.justicexchange.commons.utility.Utility;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Properties;
/**
* Properties DAO cache object.
* NOTE: This is the real cache. It had to be made into another class because Spring will not proxy a proxied object.
* Meaning that the cached method was being called by another method in the class and this prevented the properties
* file from being cached. It is better to cache the whole properties file instead of each property. This leads to
* one read every n minutes instead of x reads every n minutes.
* <p>Note
In proxy mode (which is the default), only external method calls coming in through the proxy
are intercepted. This means that self-invocation, in effect, a method within the target object
calling another method of the target object, will not lead to an actual caching at runtime even
if the invoked method is marked with @Cacheable - considering using the aspectj mode in
this case.</p>
*/
@Repository
public class PropertiesDao {
/**
* Reads the properties from the webdav for this environment.
* @return Properties object
*/
@Cacheable("GlobalPropertiesDao.getProperties")
public Properties getProperties() throws IOException {
Properties properties = new Properties();
String response = HttpUtils.getResponse(Utility.getWebdavServer() +
"/product/jxportal/config/jxportal.properties");
if (StringUtils.isNotBlank(response)) {
//noinspection ConstantConditions
properties.load(new ByteArrayInputStream(response.getBytes()));
}
return properties;
}
}
Finally, problem solved.
Like this:
Like Loading...