My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
CachingAnnotations  
Introduction to caching annotations
Featured
Updated Sep 24, 2010 by radomi...@gmail.com

Caching Annotations

GJUtil provides a set of Java annotations that allow developers to simply cache method results to GAE Memcache or other context/scope (e.g. application instance or HTTP request).

Depending on cache implementation and usage context, object being cached must implement java.io.Serializable. This is required if you want to store your objects to the App Engine's Memcache.

GJUtil depends on Guice for annotations to work. All classes containing GJUtil annotations must be instantiated (or injected) via Guice; otherwise, the annotations will not work.

Usage Examples

// The first call caches returned User object with default cache provider.
// (The method signature and arguments will be used for the cache key generation.)
// Subsequent method invocations will return cached object (if it's still available) without executing the method.
@MemCacheable
public User getUser(Long id) {
  // some lengthy operation...
  return DAO.findUser(id); // example
}

// Cache Balance object returned by the method with a key generated using the specified group name and the method argument
@MemCacheable(group="balance")
public Balance getBalance(String account) {
  ...
}

// For the same account value, overrides balance object in cache set by the above method
@MemCacheable(group="balance")
public void updateBalance(String account, Balance balance) {}

// Cleanup the cache before the method is invoked
// (Depending on how you structure your cache, you may not have to use this annotation.
// As an alternative, you can directly call cleanup() method of the cache provider.)
@MemCacheCleanup
public void someMethod() {
  ...
}

Annotation Reference

@com.googlecode.gjutil.caching.MemCacheable

  • Can be placed on a method that returns a value.
  • Value returned by the method will be put to cache using a key generated from all method arguments and the method signature.
  • If optional group parameter is specified, it will be used as a part of the caching key instead of the method signature.
  • If optional cache parameter is specified, it will be used for cache provider lookup. Otherwise, default cache provider will be used.
  • Optional expirationSeconds parameter overrides default expiration of cache provider.

@com.googlecode.gjutil.caching.MemCacheSetter

  • Use this annotation to mark a method that you want to use for putting an object to cache (leave the method body empty).
  • The marked method must have at least two arguments.
  • The value of annotation's group parameter and all method arguments except the last one will be used for cache key generation. (Order of arguments must be the same as for the method marked with @MemCacheable. Otherwise, generated cache key will probably differ and you will not be able to retrieve cached object.)
  • The last method argument should be an object being cached.
  • Optional expirationSeconds parameter overrides default expiration cache provider.

@com.googlecode.gjutil.caching.MemCacheCleanup

  • Can be placed on a method.
  • Triggers cache cleanup (for all cache providers that implement it) before method execution.

Cache Configuration

You should configure your cache and Guice injector as soon as the application is started. One way to do this is extending GuiceServletContextListener.

If you want to stick to GAE Memcache only, you can initialize GJUtil simply with:

Injector injector = Guice.createInjector(new CachingInterceptorsModule());

However, GJUtil is very flexible and allows you to define as many different caches as you want, with different expiration times and scopes.

CacheProvidersRegistry registry = new DefaultCacheProvidersRegistry();

// Use App Engine Memcache for default cache
registry.setDefaultCacheProvider(new GAEMemcacheProvider());

// Add one more Memcache provider with explicitly set options.
// It can be referenced via annotation using "shared" for cache name.
GAEMemcacheProvider globalsCache = new GAEMemcacheProvider("Shared Memcache");
globalsCache.setCacheNull(false);
globalsCache.setExpirationSeconds(1800);
registry.addCacheProvider(globalsCache, "shared");

// Create cache for server instance
AbstractCacheProvider serverCache = new SimpleCacheProvider();
serverCache.setExpirationSeconds(600);
registry.addCacheProvider(serverCache, "server");

// Create HTTP request bound session
// This is equivalent to storing object in the request scope
// but you have flexibility to easily change to another cache provider
AbstractCacheProvider requestCache = new SimpleCacheBasedProvider() {

  @Override
  protected SimpleCache getCache() {
    // RequestContextHolder used here is something provided by your framework or you could
    // create it on your own using ThreadLocal and servlet filter or some other interceptor
    HttpServletRequest req = RequestContextHolder.getRequest();
    if(req != null) {
      SimpleCache cache = (SimpleCache) req.getAttribute(ATTR_CACHE);
      if(cache == null) {
        cache = new SimpleCache();
        req.setAttribute(ATTR_CACHE, cache);
      }
      return cache;
    }
    return null;
  }

  @Override
  public void cleanup() {
    // do nothing... there's no need to cleanup cache that will be rejected soon.
  }
};
registry.addCacheProvider(requestCache, "request");


// Finally, initialize Guice with a module interceptor that takes your cache registry
Injector injector = Guice.createInjector(new MyCachingInterceptorsModule(registry));

class MyCachingInterceptorsModule extends CachingInterceptorsModule {
  CacheProvidersRegistry registry;

  MyCachingInterceptorsModule(CacheProvidersRegistry registry) {
    this.registry = registry;
  }

  @Override
  protected CachingInterceptor createCachingInterceptor() {
    return new CachingInterceptor(registry);
  }
}

How does cache referencing work?

When adding a cache provider to the registry, you can assign as many names to it as you want:

...
registry.addCacheProvider(globalsCache, "shared", "global", "countries");

registry.addCacheProvider(requestCache, "request", "user", "balance");
...

Listed cache references may be used in annotation parameters cache and group, with somewhat different effects:

@MemCacheable(cache="request")
public User getUser(Long id) { ... }

explicitly chooses the "request" cache.

@MemCacheable(cache="shared", group="user")
public User getUser(Long id) { ... }

@MemCacheSetter(cache="shared", group="user")
public void cacheUser(Long id, User user) { }

will put user to the "shared" cache but cache key will be generated using group identifier "user" and id method argument.

@MemCacheable(group="balance")
public Balance getBalance(String account) { ... }

@MemCacheable(group="balance")
public void updateBalance(String account, Balance balance) {}

goes to the requestCache as it's associated with name "balance".


Sign in to add a comment
Powered by Google Project Hosting