Using SelfPopulatingCache in Ehcache
Join the DZone community and get the full member experience.
Join For FreeOften 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
Published at DZone with permission of Mikhail Kolesnik. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments