Main Elements of the Framework

Caching Properties

We configure all of the caching parameters in a properties file called cache.ccf. These parameters include caching information such as maximum number of objects that can be stored in memory, time to live (after which the cached data is automatically released from the memory), idle time (elapsed time since last access time), memory cache name (i.e., caching algorithm such as LRU or MRU), etc. In the current version of JCS, the cache properties file is in plain text format. SpiritCache framework, a commercial implementation of JCache API from SpiritSoft, supports the cache configuration in XML format.

Make sure this properties file is stored in the classpath. Note: JCS provides a method to specify a configuration file name, in case you want to use a different file to store cache properties. Check the JCS Javadocs on how to read cache configuration parameters from a file other the default properties file.

Listed below are the Java classes that a web application needs to know to use the caching functionality. These classes are located in common.caching package in the source code provided with this article. Javadocs for these classes are also included in the source code .zip file. (The class diagram in Figure 2 shows the relationship between these Java classes.)

ICacheManager

This is the main interface (contract) that a client program uses to handle all of the operations related to caching (i.e., storing, accessing, and releasing the data in the cache). The client program can be a JSP, Struts Action class, or a POJO (Plain Old Java Object). This interface was created to hide all of the caching implementation details from the client so if we need to switch to a different third-party caching API in the future, we wouldn't need to change any of the client code.

BaseCacheManager

This is the main class in web portal caching framework. It's the base implementation of the ICacheManager interface. BaseCacheManager was created to centralize all of the cache-related methods in a single class. It's designed as a singleton to ensure that there is one and only one instance of ICacheManager created in the servlet container's JVM. In a clustered environment where multiple web server/servlet container instances are accepting the web requests, there will be a separate ICacheManager instance created in each JVM. If you switch to a different caching API in the future, this is the only class that needs to be modified to work with the new cache API. If you switch to a JCache-compliant caching implementation, the required changes in the cache manager should be minimal.

ICacheLoader

This interface is used to implement the actual data access logic in the web client. All of the client programs that need to use the caching mechanism must implement this interface. It has a single method called loadCacheObject() and takes two input parameters: a String to specify the cache region name and an Object to specify the cache key. This way, the cache manager will know which client program to use (to execute the loadCacheObject method) to reload the object in the cache when the cached data is expired after the specified "time to live" is elapsed.

ICacheKey

The ICacheKey interface was created to hide the specific logic used to create a cache key. Sometimes the cache key may not be a simple string. It may be as complex as the combination of multiple objects, and getting these values from the data source involves several lookup methods instead of one single method. In this case, the ICacheKey interface can be used to define all of the complex logic involved in creating the cache key. This way, the cache-key creation logic will be defined in a separate class. I wrote a sample class called TestCacheKey that implements this interface and overrides the method getCacheKey() to give an idea about using this interface.

CacheRegions

A cache region is defined as an organizational namespace for holding a collection of cache objects. You need to define the cache regions in the configuration file to store the data in separate memory spaces in the cache to manage controlled expiration of the cached data. Objects with similar characteristics (such as time to live and business use) should be cached in the same cache region so that they can all be invalidated at the same time, if needed. I defined separate cache regions to store the lookup data and rate revision data. To eliminate any synchronization issues that could cause poor performance, I used a separate instance of Cache (JCS) for each cache region.

CacheElementInfo

This class is used to encapsulate all of the caching statistics (such as hit count, miss count, hit rate, etc.) that can be used to monitor the effectiveness of caching each object in the web application.

Compile, Build, and Unit Testing

I created a build script using Ant to compile all Java source code for my object-caching framework. The Ant build script, named build.xml, is located in the WEB-INF\classes directory. I also wrote a JUnit test client to test different caching scenarios using the web portal caching framework. This test script, called CachingTestCase, is located in the WEB-INF\classes\common\caching\test directory. Extract the sample code to a new web application directory, and to test the JUnit test script, run the following commands from command line.

Change directory to the %TOMCAT_HOME%/webapps/web-app-name/WEB-INF/classes directory (in a Unix-like environment, this would be $TOMCAT_HOME/webapps/web-app-name/WEB-INF/classes).

Run the following commands:

ant common.compile
To compile all of the Java classes included in the caching framework.

ant common.runjunit
To run the JUnit test script. The test script uses Log4J API to display all of the output messages.

Guidelines to Consider When Using Object Caching

Follow these guidelines when you decide to cache a specific type of data in your web application. Caching should be applied carefully when other means, such as the data access itself, cannot be further improved. Caching can introduce some complexity, complicating the maintenance of the overall solution; therefore, consider the trade-off between performance and complexity before applying caching.

When considering the use of a cache, consider the expected lifetime of the objects and the refresh rate or time-to-live values associated with those objects. The cache cannot hold all of the data we would like to store, so the memory used by the cache needs to be released from time to time, either by defining a reasonable time-to-live attribute or by explicitly invalidating the cached objects when the data is no longer needed. Caching algorithms such as Least Recently Used (LRU) or Least Frequently Used (LFU) are specified so that the cache will release the object based on the frequency of access. Jack Shirazi's book Java Performance Tuning provides a very interesting discussion on the caching topic, discussing what type of data should be cached and guidelines to consider when using caching.

Note that the caching framework does not handle the creation of objects that need to be cached in a web application (i.e., the data access logic to retrieve the data from the data source is not coded in the caching classes). It relies on the client program to define the actual data-access logic. Technologies like Java Data Objects (JDO) are typically used to encapsulate the data access logic in an enterprise web application. Refer to O'Reilly's Java Data Objects to learn more on how to separate data access logic from business logic.

Conclusion

This article provides an overview of an object-caching framework developed for a web portal application using Jakarta's Java Caching System (JCS). The framework is very flexible and can be reused in any web application or even in a client/server Java application. This article covered the main elements of a web portal caching framework in detail and a JUnit test script to test the caching framework for various scenarios.

JCS was built as a system close to JCACHE Java Temporary Caching API (JSR-107), a description of the caching system used in Oracle 9i and other popular caching frameworks. This specification may end up as a Java extension framework in a future JDK release. One of my objectives was to keep the web portal caching framework loosely coupled with JCS. This way, if I need to switch to a different framework (such as JCache) in the future, I could accomplish the transition without major code changes in the web portal application client code.

I am currently logging (using Log4J API) cache monitoring information such as hit count, miss count, and hit rate to measure the effectiveness of caching. There may be other parameters that need to be monitored in the future to assess the impact of caching on performance. Also, to measure the response times for data access with and without using the caching, a load-testing tool such as Grinder or JMeter could be used to test scalability and performance issues.

Keeping caching in sync in a clustered environment will be a challenge, since each servlet container would have a cache manager instance in its JVM. The solution to this problem is to create a Message Driven Bean (MDB) to notify all of the cache managers when to refresh the cached data.

Conventional methods for object lookup, such as a simple hashtable, JNDI, or even EJB, provide a way to store an object in memory and perform the object lookup based on a key. But none of these methods provide any mechanism for removal of the object from memory when it's no longer needed, or automatic creation of object when it's accessed after expiration. The HttpSession object (in the servlet package) also allows objects to be cached, but it does not have the concepts of sharing, invalidation, per object expiration, automatic loading, or spooling which are the main elements of a caching framework.

Though the integration of caching functionality into a web application does involve additional design and development effort, I think the caching benefits outweigh the extra work. I have seen a great improvement in my web application's performance, especially in accessing the lookup data and search results, after I implemented the caching framework. This web application module is currently in the testing phase. In the near future, I will post some benchmarks on the performance results (with and without using caching) to compare how effective object caching can be in designing a faster and scalable web application.