The Map-based reference implementation
of our example application does not implement any housekeeping, which
will eventually lead to OutOfMemory errors as the
Map
grows.
This part shows how to replace the ConcurrentMap with a Cache and utilize the
Cache’s eviction to limit the amount of memory used.
The following describes the Ehcache example.
Instead of using a ConcurrentMap,
a net.sf.ehcache.Cache
instance is created. As described in the JSR 107 overview, the Cache
instance can be obtained through a CacheMangaer.
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
Cache cache = CacheManager.getInstance().getCache("events");
ServletContext context = servletContextEvent.getServletContext();
context.setAttribute(CACHE, cache);
}
Each CacheManager in Ehcache corresponds to an XML configuration file.
In the example above, CacheManager was used as a Singleton without specifying
a file name. In that case, the CacheManger searches for the default file
called ehcache.xml in the Classpath and
configures its caches accordingly.
The following is a simple example of ehcache.xml, as used in the
application:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="false" monitoring="autodetect"
dynamicConfig="true">
<cache name="events"
maxEntriesLocalHeap="1000"
eternal="true"
overflowToDisk="false"
memoryStoreEvictionPolicy="LFU">
</cache>
</ehcache>
The file defines a cache by the name “events”. The cache size is limited to
1000 entries, and uses the LFU
policy for eviction. The cache named “events” is created in the initialization
code above.
Instead of configuring Ehcache through an XML file, it is also possible
to use Ehcache’s fluent
Java API for programmatic configuration.
Within the REST interface, the cache is accessed via the
ServletContext.
// ...
@Context
private ServletContext context;
private Cache cache;
@PostConstruct
public void init() {
cache = (Cache) context.getAttribute(CACHE);
}
Ehcache starts maintenance threads, like the eviction thread for housekeeping,
or the threads sending heartbeat messages to other ehcache instances.
In order to terminate these threads, shutdown
must be called. As described in the JSR 107 overview, the
CacheManager manages the Caches’ life cycle, i.e. the Cache’s maintenance
threads can be terminated using the CacheManager Singleton:
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
CacheManager.getInstance().shutdown();
}
As Ehcache’s API is based on ConcurrentMap, the write method is similar
to the ConcurrentMap-based reference implementation:
@POST
@Path("{user}")
@Consumes(MediaType.APPLICATION_JSON)
public void appendEvent(@PathParam("user") String user, String msg) {
boolean success;
cache.putIfAbsent(new Element(user, UserEventList.emptyList()));
do {
Element oldElement = cache.get(user);
UserEventList oldList = (UserEventList) oldElement.getObjectValue();
UserEventList newList = UserEventList.append(oldList, msg);
Element newElement = new Element(user, newList);
success = cache.replace(oldElement, newElement);
}
while ( ! success );
}
Unlike ConcurrentMap, Ehcache wraps key/value pairs in
Element objects.
The wrapper is used to store additional information about the entry,
such as the last access time and the expiration time used for eviction.
Apart from that, the API is the same.
Just like the write method, read does not differ from the
Map-based implementation, except that Key/Value pairs are wrapped
in a Element objects:
@GET
@Path("{user}")
@Produces(MediaType.APPLICATION_JSON)
public List<String> searchByUser(@PathParam("user") String user) {
Element result = cache.get(user);
if ( result == null ) {
return new ArrayList<>();
}
return ((UserEventList) result.getObjectValue()).getMessages();
}
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.7.0</version>
</dependency>
Our example code is hosted on GitHub.
The example can be run with maven:
mvn tomcat7:run-war -pl part02.ehcache -am verify
The Web interface is then accessible via
http://localhost:8080.
As the local-only deployment involves no network interaction, the
performance is mainly influenced by the speed of serialization.
Ehcache can be configured
with copyOnRead="false"
and copyOnWrite="false"
, which will disable serialization in local-only deployments, and store
references instead of serialized objects in the cache.
For local-only deployments, this makes Ehcache faster than for example Hazelcast,
which always performs serialization.
This page showed how the ConcurrentMap in our example can be replaced with
a local Ehcache. The following pages show how the same can be done with
Hazelcast and Infinispan: