I am developing a Java web application that bases it behavior through large XML configuration files that are loaded from a web service. As these files are not actually requi
Your question contains several separate questions together. Let's start slowly. ServletContext is good place where you can store handle to your cache. But you pay by having cache per server instance. It should be no problem. If you want to register cache in wider range consider registering it into JNDI.
The problem with caching. Basically, you are retrieving xml via webservice. If you are accesing this webservice via HTTP you can install simple HTTP proxy server on your side which handle caching of xml. The next step will be caching of resolved xml in some sort of local object cache. This cache can exists per server without any problem. In this second case the EHCache will do perfect job. In this case the chain of processing will be like this Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice)
.
Pros:
Cons:
Update: don't forget to always send HTTP HEAD request into proxy to ensure that cache is up to date.
Here's an example of caching with EhCache. This code is used in several projects to implement ad hoc caching.
1) Put your cache in the global context. (Don't forget to add the listener in WEB.XML).
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
public class InitializationListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
CacheManager singletonManager = CacheManager.create();
Cache memoryOnlyCache = new Cache("dbCache", 100, false, true, 86400,86400);
singletonManager.addCache(memoryOnlyCache);
cache = singletonManager.getCache("dbCache");
ctx.setAttribute("dbCache", cache );
}
}
2) Retrieve the cache instance when you need it. i.e. from a servlet:
cache = (Cache) this.getContext().getAttribute("dbCache");
3) Query the cache just before you do an expensive operation.
Element e = getCache().get(key);
if (e != null) {
result = e.getObjectValue(); // get object from cache
} else {
// Write code to create the object you need to cache, then store it in the cache.
Element resultCacheElement = new Element(key, result);
cache.put(resultCacheElement);
}
4) Also don't forget to invalidate cached objects when appropriate.
You can find more samples here
After doing some more looking around myself, it seems that the easiest way to achieve what I need (within the requirements and acceptable limitations described in the question), would be to add my caching object to the Servlet Context, and looking it up (or passing it around) where needed.
I'd just instantiate my configuration loader from a ServletContextListener, and within the contextInitialized() method, I'd just store it into the ServletContext using ServletContext.setAttribute(). It's then easy to look it up from the servlets themselves using request.getSession().getServletContext().getAttribute().
I suppose this is the proper way to do it without introducing spring or any other dependency injection framework.
I did not had any problems with putting cached object instance inside ServletContext. Do not forget other 2 options (request scope, session scope) with setAttributes methods of this objects. Anything that is supported natively inside webcontainers and j2ee serveers is good (by good I mean it's vendor independed, and without heavy j2ee librarires like Spring). My biggest requirements is that servers gets up and running in 5-10 seconds.
I really dislike all caching solution, beacuse it's so easy to get it working on local machine, and hard to get it working on production machines. EHCACHE, Infinispan etc.. Unless you need cluster wide replication / distribution, tightly integrated with Java ecosystem, you can use REDIS (NOSQL datatabase) or nodejs ... Anything with HTTP interface will do. Especially
Caching can be really easy, and here is the pure java solution (no frameworks):
import java.util.*;
/*
ExpirableObject.
Abstract superclass for objects which will expire.
One interesting design choice is the decision to use
the expected duration of the object, rather than the
absolute time at which it will expire. Doing things this
way is slightly easier on the client code this way
(often, the client code can simply pass in a predefined
constant, as is done here with DEFAULT_LIFETIME).
*/
public abstract class ExpirableObject {
public static final long FIFTEEN_MINUTES = 15 * 60 * 1000;
public static final long DEFAULT_LIFETIME = FIFTEEN_MINUTES;
protected abstract void expire();
public ExpirableObject() {
this(DEFAULT_LIFETIME);
}
public ExpirableObject(long timeToLive) {
Expirer expirer = new Expirer(timeToLive);
new Thread(expirer).start();
}
private class Expirer implements Runnable {
private long _timeToSleep;
public Expirer (long timeToSleep){
_timeToSleep = timeToSleep;
}
public void run() {
long obituaryTime = System.currentTimeMillis() + _timeToSleep;
long timeLeft = _timeToSleep;
while (timeLeft > 0) {
try {
timeLeft = obituaryTime - System.currentTimeMillis();
if (timeLeft > 0) {
Thread.sleep(timeLeft);
}
}
catch (InterruptedException ignored){}
}
expire();
}
}
}
Please refer to this link for further improvements.
Option #1: Use an Open Source Caching Library Such as EHCache
Don't implement your own cache when there are a number of good open source alternatives that you can drop in and start using. Implementing your own cache is much more complex than most people realize and if you don't know exactly what you are doing wrt threading you'll easily start reinventing the wheel and resolving some very difficult problems.
I'd recommend EHCache it is under an Apache license. You'll want to take a look at the EHCace code samples.
Option #2: Use Squid
An even easier solution to your problem would be to use Squid... Put Squid in between the process that requests the data to be cached and the system making the request: http://www.squid-cache.org/
Bref , you can use this ready spring ehcache configuration
1- ehcache.xml : show global configuration of Ehcache.
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true" name="myCacheManager">
<!--
see ehcache-core-*.jar/ehcache-fallback.xml for description of elements
Attention: most of those settings will be overwritten by hybris
-->
<diskStore path="java.io.tmpdir"/>
</ehcache>
2- ehcache-spring.xml : create EhCacheManagerFactoryBean and EhCacheFactoryBean.
<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
scope="singleton">
<property name="configLocation" value="ehcache.xml" />
<property name="shared" value="true" />
</bean>
<bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean" scope="singleton">
<property name="cacheManager" ref="myCacheManager" />
<property name="cacheName" value="myCache" />
<property name="maxElementsInMemory" value="1000" />
<property name="maxElementsOnDisk" value="1000" />
<property name="eternal" value="false" />
<property name="diskPersistent" value="true" />
<property name="timeToIdle" value="600" />
<property name="timeToLive" value="1200" />
<property name="memoryStoreEvictionPolicy" value="LRU" />
<property name="statisticsEnabled" value="true" />
<property name="sampledStatisticsEnabled" value="true" />
</bean>
3- Inject "myCache" bean in your business class , see the following exemple to get started with getting and putting a object in your cache.
@Resource("myCache")
private net.sf.ehcache.Cache myCache;
@Resource("myService")
private Service myService;
public byte[] getFromCache(final String code)
{
// init Cache
final StringBuilder builder = new StringBuilder();
// key to identify a entry in cache map
final String key = code;
// get form the cache
final Element element = myCache.get(key);
if (element != null && element.getValue() != null)
{
return (byte[]) element.getValue();
}
final byte[] somethingToBeCached = myService.getBy(code);
// store in the cache
myCache.put(new Element(key, somethingToBeCached));
return somethingTobeCached;
}