Mikhail has posted 3 posts at DZone. View Full User Profile

Using SelfPopulatingCache in Ehcache

09.02.2009
| 11757 views |
  • submit to reddit

Often you can notice that Ehcache is used mostly like tool that implements highly configurable maps. Sometimes developer configure time-to-live properties, sometimes he or she is making use of disk-store functionality, but you can rarely meet someone who has cache population/eviction process that makes sense.

In perfect world I would expect cache to be a universal pool. I request a value for key and would like to get it whenever it is possible and as fast as possible with just one line of code. But in reality I usually can see huge cache population code on the application startup, daemon threads that update caches in eternal loop and numerous “ifs” around cache.get() calls.

In strive to the perfect world I’ve discovered a SelfPopulatingCache class in Ehcache. In this article I will describe how using SelfPopulatingCache class one can implement a self creating with optional auto-updating. In some way this example is an implementation of ideas mentioned in Ehcache documentation.

Example

What you will see down here:

  • A Reader that fetches an object from cache 5 times every 0.5 seconds.
  • Behind scenes cache will create a new object if there is no such object is there in cache for a key requested.
  • A daemon thread will trigger cache refresh every 2 seconds.

The most important class in this example is ExampleCacheProvider.

package com.blogspot.mikler.java;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;

public class ExampleCacheProvider {
private CacheManager cacheManager;
private CacheEntryFactory updatingFactory;
public SelfPopulatingCache selfPopulatingCache;

public ExampleCacheProvider() throws Exception {
cacheManager = CacheManager.create();
Ehcache originalCache = cacheManager.getCache("com.blogspot.mikler.java.cache");

final String cacheType = System.getProperty("com.blogspot.mikler.java.cache.factory");
if (cacheType == null || cacheType.equals("create")){
updatingFactory = new ExampleCacheEntryFactory();
} else {
updatingFactory = new ExampleUpdatingCacheEntryFactory();
}
selfPopulatingCache = new SelfPopulatingCache(originalCache, updatingFactory);
//chache refresh thread
Thread updatingThread = new Thread(){
public void run() {
super.run();
while (true){
System.out.println("!!!!! Doing refresh !!!!!");
selfPopulatingCache.refresh();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}
};
updatingThread.setDaemon(true);
updatingThread.start();

}

public Ehcache getCache(){
return selfPopulatingCache;
}
}

What it does is creating simple Ehcache (original cache) and wrapping it with SelfPopulatingCache (selfPopulatingCache). We can use this class the same way as CacheManager, so one can consider extending CacheManager, but to keep it simple here we'll stick to this kind of cache source.
While wrapping we specify updatingFactory, which can be set to one of two options: ExampleCacheEntryFactory (line 19) and ExampleUpdatingCacheEntryFactory(line 21). For the purposes of this example the choice between this two options is done based on the “com.blogspot.mikler.java.cache.factory” system property value. The difference between this two is that ExampleCacheEntryFactory implements CacheEntryFactory interface, while ExampleUpdatingCacheEntryFactory extends ExampleCacheEntryFactory and implements UpdatingCacheEntryFactory. The difference between using each of this options is explained later on. Meanwhile here is the code of both classes.

package com.blogspot.mikler.java;

import net.sf.ehcache.constructs.blocking.CacheEntryFactory;

public class ExampleCacheEntryFactory implements CacheEntryFactory {

public Object createEntry(Object key) throws Exception {
System.out.println("++++++creating entry for key = " + key);
return new StringBuffer(Long.toString(Math.round(100*Math.random())) + key+"0");
}
}

ExampleUpdatingCacheEntryFactory :

package com.blogspot.mikler.java;

import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;

public class ExampleUpdatingCacheEntryFactory
extends ExampleCacheEntryFactory
implements UpdatingCacheEntryFactory
{
public void updateEntryValue(Object key, Object value) throws Exception {
System.out.println("~~~~~~UPDATING entry for key = " + key);
final StringBuffer stringBuffer = (StringBuffer) value;
stringBuffer.append(stringBuffer.length());
}

}

As you can see in this example as cache element’s key string used, while StringBuffer is used as a value. In createEntry() method in ExampleCacheEntryFactory StringBuffer is created with leading random number. While in updateEntryValue() method of ExampleUpdatingCacheEntryFactory existing StringBuffer length is appended to the buffer itself.

And finally, here goes our Reader main class. It fetches our wrapped cache, get’s value for “foo” key from it and stores it into final local variable fooOriginalBuffer, that is never changed late in Reader’s code. Then it starts doing 5 iterations of getting value for “foo” key from cache, displaying debug info and stats, and sleeping for one second. The code is simple as this.

package com.blogspot.mikler.java;

import net.sf.ehcache.Ehcache;

public class Reader {
private ExampleCacheProvider exampleCacheProvider;

public Reader(ExampleCacheProvider exampleCacheProvider) {
this.exampleCacheProvider = exampleCacheProvider;
}

public void run(){
Ehcache cache = exampleCacheProvider.getCache();
final StringBuffer fooOriginalBuffer = (StringBuffer) cache.get("foo").getValue();
for (int i = 0; i < 5; i++){
System.out.println("----------------------------------");
System.out.println("Starting iteration " + i);
StringBuffer fooBuffer = (StringBuffer) cache.get("foo").getValue();
System.out.println("fooBuffer. = " + fooBuffer.toString());
System.out.println("fooOriginalBuffer = " + fooOriginalBuffer.toString());
System.out.println("cache.getSize() = " + cache.getSize());
System.out.println("----------------------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}

public static void main(String[] args) throws Exception {
ExampleCacheProvider cacheProvider = new ExampleCacheProvider();
Reader reader = new Reader(cacheProvider);
reader.run();
}
}

Let’s run it with both updatingFactory options.
Output of running the Reader with ExampleCacheEntryFactory (-Dcom.blogspot.mikler.java.cache.factory=create)

!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 0
fooBuffer. = 35foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 1
fooBuffer. = 35foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 2
fooBuffer. = 36foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 3
fooBuffer. = 36foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 4
fooBuffer. = 51foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------

And here how the output looks like while running Reader with ExampleUpdatingCacheEntryFactory as updatingFactory (-Dcom.blogspot.mikler.java.cache.factory=update)

!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 0
fooBuffer. = 19foo0
fooOriginalBuffer = 19foo0
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 1
fooBuffer. = 19foo0
fooOriginalBuffer = 19foo0
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
~~~~~~UPDATING entry for key = foo
----------------------------------
Starting iteration 2
fooBuffer. = 19foo06
fooOriginalBuffer = 19foo06
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 3
fooBuffer. = 19foo06
fooOriginalBuffer = 19foo06
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
~~~~~~UPDATING entry for key = foo
----------------------------------
Starting iteration 4
fooBuffer. = 19foo067
fooOriginalBuffer = 19foo067
cache.getSize() = 1
----------------------------------

Conclusion

As you can see the output is quite different and here are some conclusions we come to from analyzing it:

  • No NullPointerException is occurs while trying to get object from cache that is not there. It is being created in both cases.
  • If updatingFactory is instance of CacheEntryFactory (ExampleCacheEntryFactory in our case) when cache.refresh() is called each object in cache is being recreated.
  • Also when updatingFactory is instance of CacheEntryFactory final fooOriginalBuffer variable is not updated.
  • Meanwhile in case when updatingFactory is instance of CacheEntryFactory (UpdatingCacheEntryFactory in our case) when cache.refresh() is called each object in cache gets updated instead of being recreated.
  • And final fooOriginalBuffer variable value is updated as well. (Actually this variable itself is passed to updateEntryValue() method of ExampleUpdatingCacheEntryFactory)

Besides the above conclusion, one can notice that using SelfPopulatingCache reduces scattering and tangling (see my “WTF is AOP article”).

As usually, you can get source code for this post by running
svn co http://miklerjava.googlecode.com/svn/trunk/samples/SelfUpdatingEhCache SelfUpdatingEhCache

 

 

References
Published at DZone with permission of its author, Mikhail Kolesnik. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)