Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

I am using a Springframework project and 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
    )
  • The spring context was configured for annotated cache beans
  • 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.

February 26th, 2013

Posted In: Ehcache, Java, Spring

Tags: , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

WP to LinkedIn Auto Publish Powered By : XYZScripts.com