«Top»
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.
Initialization
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);
}
Shutdown
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();
}
Write
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.
Read
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<>();