badge

Say, you have a method that takes time to execute and you want its result to be cached. There are many solutions, including Apache Commons JCS, Ehcache, JSR 107, Guava Caching and many others.

jcabi-aspects offers a very simple one, based on AOP aspects and Java6 annotations:

import com.jcabi.aspects.Cacheable;
public class Page {
  @Cacheable(lifetime = 5, unit = TimeUnit.MINUTES)
  String load() {
    return new URL("http://google.com").getContent().toString();
  }
}

The result of load() method will be cached in memory for five minutes.

How It Works?

This post about AOP, AspectJ and method loging explains how "aspect weaving" works (I highly recommend that you read it first).

Here I'll explain how caching works.

The approach is very straight forward. There is a static hash map with keys as "method coordinates" and values as their results. Method coordinates consist of the object, an owner of the method and a method name with parameter types.

In the example above, right after the method load() finishes, the map gets a new entry (simplified example, of course):

key: [page, "load()"]
value: "<html>...</html>"

Every consecutive call to load() will be intercepted by the aspect from jcabi-aspects and resolved immediately with a value from the cache map. The method will not get any control until the end of its lifetime, which is five minutes in the example above.

What About Cache Flushing?

Sometimes it's necessary to have the ability to flush cache before the end of its lifetime. Here is a practical example:

import com.jcabi.aspects.Cacheable;
public class Employees {
  @Cacheable(lifetime = 1, unit = TimeUnit.HOURS)
  int size() {
    // calculate their amount in MySQL
  }
  @Cacheable.FlushBefore
  void add(Employee employee) {
    // add a new one to MySQL
  }
}

It's obvious that the number of employees in the database will be different after add() method execution and the result of size() should be invalidated in cache. This invalidation operation is called "flushing" and @Cacheable.FlushBefore triggers it.

Actually, every call to add() invalidates all cached methods in this class, not only size().

There is also @Cacheable.FlushAfter. The difference is that FlushBefore guarantees that cache is already invalidated when the method add() starts. FlushAfter invalidates cache after method add() finishes. This small difference makes a big one, sometimes.

This article explains how to add jcabi-aspects to your project.

Discuss it at: reddit hackernews