Using SelfPopulatingCache in Ehcache
Join the DZone community and get the full member experience.
Join For FreeOften you will notice that Ehcache is used mostly like a tool that implements highly configurable maps. Sometimes developers configure time-to-live properties, or make use of disk-store functionality, but you can rarely meet someone who has cache population/eviction process that makes sense.
In the perfect world I would expect cache to be a universal pool. I request a value for a 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 my 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 cache with self creating objects 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() {
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");
}
}
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)
Instructions about how to check out the source code for this article you can find in the original post.
Originally posted on miklerjava.blogspot.com
Opinions expressed by DZone contributors are their own.
Comments