Transparent Caching with Aspect-Oriented Programming
One reason I like aspect-oriented programming, or AOP, is that it lets me add functionality to an existing API transparently without changing the API itself. I’d like to show what AOP can do by implementing caching for a database access layer.
Every application has bits of code that cut across all its layers without belonging to any one of them. That might be code for logging and debugging, security, auditing, synchronization, and so on. We can call these bits aspects. With AOP we can weave those aspects through the existing code without modifying that code.
For example, with AOP we can easily write an aspect that logs any exception thrown from the API without adding a line to the API code. Broadly speaking there are two kinds of aspects, development and production. Development aspects serve development purposes such as tracing or measuring performance. They’re part of the application only during development. Production aspects stay in the application and provide system features such as transactions or security.
AOP basics
Whichever AOP framework you use, some terms are common to all of them. You need to understand them to understand aspect-oriented programming.
Join Point
Defines points execution can reach, such as a method call, a constructor call, or static initialization.
Pointcut
Defines a language for selecting individual join points.
Advice
Lets you specify code to run when the pointcut matches. In effect, when this happens, do that.
Put a join point, a pointcut and advice together and you have an aspect that does something. We write aspects in ordinary Java syntax combined with AOP syntax. AOP introduces new constructs such as pointcut, around, before and call. The figure below illustrates what an aspect looks like.

Full-size image (about 25 KB)>
The question is how to integrate the aspect into our code. That’s the job of the AOP framework, and it’s called weaving. A weaver does the work, and it can happen at different points in the Java code’s life.
Weaver
The weaver takes classes in and produces classes out. This can happen at four points.
Compile-time weaving
This takes place during compilation. The source is available and gets compiled, producing compiled code that includes the aspects. The aspects can be supplied as source files or in compiled form.
Post-compile weaving
This runs on already compiled code and again produces compiled code. The aspects can be supplied as source files or in compiled form.
Load-time weaving
This happens when the class loader loads compiled code into the JVM. It requires a special weaving class loader.
Runtime weaving
This changes compiled code that has already been loaded into the JVM, without reloading the class.
Of course, which kinds of weaving are supported depends on the framework or weaver implementation. For example, AspectJ, which I’m using for this article, supports all the approaches above except runtime weaving.
Caching with AOP
Caching is a good example of something that cuts across the whole system. What you need to cache, and where, can depend on the deployment, because the data and load differ. In the worst cases, people slip ad hoc “caching” directly into the code in all sorts of roundabout ways. AOP lets us handle it cleanly without touching the existing code.
For an example, suppose we have a data access layer built with something like JDBC and want to cache it. I’ve chosen AspectJ, which integrates very well with the Eclipse IDE through the AJDTplugin. Besides syntax highlighting, code completion, generating specialized Javadoc and other important features, it lets us debug aspects just like any other Java application.
Choosing AspectJ doesn’t tie us to Eclipse. AspectJ has a regular compiler you can run from the command line or Ant. It also includes AspectJ Browser, a standalone GUI for AOP development. Back to the example.
The code below shows a standard data access object, or DAO, defining create, read, update and delete operations, known as CRUD. To keep it simple I haven’t infected the DAO with JDBC code. Instead, I used a java.util.Setto simulate a database table.
public class FooDao {
//pro simulaci databazove tabulky pouzijeme Set
private Set table = new HashSet();
public void update(FooBean bean){
//JDBC kod pro update objektu v databázi
//....
//my pro simulaci pouzijeme vnitrni Set
table.remove(bean);
table.add(bean);
}
public void save(FooBean bean){
//JDBC kod pro insert objektu do databáze
//....
//my pro simulaci pouzijeme vnitrni Set
table.add(bean);
}
public void delete(FooBean bean){
//JDBC kod pro smazani objektu v databázi
//....
//my pro simulaci pouzijeme vnitrni Set
table.remove(bean);
}
public FooBean findById(int id){
//JDBC kod pro smazani objektu v databázi
//....
//my pro simulaci pouzijeme vnitrni Set
FooBean wanted = new FooBean(id);
FooBean founded = null;
for (FooBean bean: table) {
if(wanted.equals(bean)){
founded = bean;
break;
}
}
return founded;
}
public List getAll(){
//JDBC kod pro smazani objektu v databázi
//....
//my pro simulaci pouzijeme vnitrni Set
return new ArrayList(table);
}
}
How do we implement the cache with an aspect? First we need to define how the cache will behave for DAO objects.
Caching rules
- Caching is per DAO object. All cached values are gathered in a group whose name corresponds to the DAO’s package and class.
- Only the results of read operations are cached.
- Any other operation on the DAO, such as deleting, updating or writing, invalidates its cache group. All objects cached for that DAO are invalidated.
We start with an aspect, which has a constructor and fields like any other Java object. Each aspect acts as a singleton unless we specify otherwise. We’ll use that to initialize the cache in the constructor. For caching, I chose Java Caching System from the Jakarta family of projects. We also define the methods for working with the cache inside the aspect. We can mark them protected in case we want a more specialized version of the aspect later. Yes, aspects can use inheritance.
public aspect CacheAspect {
private JCS cache; //instance cache manageru
/**
* Konstruktor aspektu, který inicializuje cache.
*
* @see CacheAspect#initCache()
*/
public CacheAspect() {
initCache();
}
/**
* Provede inicializaci cache. Metoda je volána pouze jednou při vytvoření
* aspektu.
*/
protected void initCache(){
try{
this.cache = JCS.getInstance("");//získáme defaultní cache
} catch (CacheException e){
throw new RuntimeException(e);
}
}
/**
* Invaliduje všechny cacheované objekty v dané skupině.
* @param groupName jméno skupiny
*/
protected void invalidate(String groupName){
this.cache.invalidateGroup(groupName);
}
/**
* Odstraní objekt z dané cacheovací skupiny.
* @param cachedObjectKey klíč objektu, který bude odstraněn
* @param groupName jméno skupiny
*/
protected void remove(String cachedObjectKey, String groupName) {
this.cache.remove(cachedObjectKey, groupName);
}
/**
* Vloží objekt do dane cache skupiny.
*
* @param cachedObjectKey klíč objektu
* @param groupName jméno skupiny
* @param cachedObject objekt, který bude cacheován
*/
protected void put(String cachedObjectKey, String groupName, Object cachedObject){
try{
this.cache.putInGroup(cachedObjectKey, groupName, cachedObject);
}catch(CacheException e){
throw new RuntimeException(e);
}
}
/**
* Vrací objekt z dané cache skupiny.
* @param cachedObjectKey klíč objektu
* @param groupName jméno skupiny
* @return cacheovaný objekt, pokud ještě není cacheováný pak null
*/
protected Object get(String cachedObjectKey, String groupName) {
return this.cache.getFromGroup(cachedObjectKey, groupName);
}
}
Now it’s time to define the pointcuts in our aspect so we can intercept individual DAO methods.
/**
* Pointcut, který zareaguje na každé volání veřejné metody get*,
* nebo find* na objektu *Dao, který leží v package
* cz.sweb.pichlik.aspectj.samples.dao.
*
Pozn:
* vzor get* je potencionálně nebezpečný, protože by se nám mohl
* aplikovat i na gettry spadající do rodiny klasických accesoru, které nemají
* nic společného s CRUD metodami.
*/
pointcut getExecs():
call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.get*(..))
|| call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.find*(..));
/**
* Pointcut, který zareaguje na každé volání veřejné metody update*,
* dalete* nebo save* na objektu *Dao,
* který leží v package cz.sweb.pichlik.aspectj.samples.dao.
*/
pointcut deleteOrUpdateOrSaveExecs():
call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.update(..))
|| call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.delete(..))
|| call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.save(..));
A pointcut has a symbolic name that we can refer to in the advice, the executable part of the aspect. We’ll write the matching rules on the assumption that the DAO objects are in the cz.sweb.pichlik.aspectj.samples.dao package and define public methods.
We’ll define two pointcuts, one for reads and one for create, update and delete operations. We intercept method calls using the keyword call. We’ll decide what the operations mean by the general rule that read operations correspond to methods matching get* and find* (the asterisk is the usual wildcard we can use here). Create, update and delete operations correspond to save, update and delete.
Now we just need to define two pieces of advice, one for reads and one for create, update and delete operations.
/**
* Advice, který obaluje metody pro získáni objektu. V případě, že objekt
* ještě není cacheován, původní metoda se zavolá a její výsledek se uloží
* do cache. V případě, že je objekt cacheován, vrací se z cache.
*
* @return cacheovaný objekt
*/
Object around(): getExecs() {
String groupName = getGroupName(thisJoinPoint);
String cachedObjectKey = getCachedObjectKey(thisJoinPoint);
//zjistime jestli je objekt cacheovan
Object result = get(cachedObjectKey, groupName);
if(result == null) { //objekt neni v cache
result = proceed();//zavolame puvodni metodu
if(result != null) {
put(cachedObjectKey, groupName, result);//vysledek ulozime do cache
}
} else {
System.out.println("Objekt:" + result + " vrácen z cache");
}
return result;
}
/**
* Advice před vykonáním smazání, vytvoření nebo změny objektu, která invaliduje celou
* cacheovanou skupinu.
*/
before(): deleteOrUpdateOrSaveExecs() {
String groupName = getGroupName(thisJoinPoint);
invalidate(groupName);
}
Advice has a type, such as around, before, after, after returning or after throwing. The type determines when the advice runs in relation to the original call: before before the method runs, after after the method runs, after returning after the method returns normally, after returning after the method throws an exception, and around wrapped around the method itself.
Advice also includes the name of the pointcut, or the pointcut expression, that triggers it. For create, update and delete operations we can use before advice and invalidate the whole cache group. For reads we need around advice.
Around advice is special in that it lets us decide whether the original method runs. We’ll use that. If the value is cached, we won’t call the original DAO method. Otherwise we call it using the keyword proceed() and store the result in the cache.
Inside each aspect we have access to the instance variable thisJoinPoint of type org.aspectj.lang.JoinPoint. That tells us the target class name, the method name, the method arguments and so on. It helps us choose the group and the key under which we store or retrieve the cached value.
/**
* Vrací jméno cacheovací skupiny. To je určeno na základě jména třídy.
* @param jp aktuální joinpoint
*/
protected String getGroupName(JoinPoint jp){
return jp.getStaticPart().getSignature().getDeclaringType().getName();
}
/**
* Vrací klíč pro cacheovaný objekt. Klíč je vypočítán na základě jména metody,
* která se volá a hodnot, které jsou předány jako argumenty (parametry)
* metody.
*
* @param jp aktuální joinpoint
*/
protected String getCachedObjectKey(JoinPoint jp){
String methodName = jp.getStaticPart().getSignature().getName();
StringBuilder key = new StringBuilder(methodName);
//z hodnot argumentu volane metody vytvorime klic cacheovaneho objektu
Object args[] = jp.getArgs();
for (Object arg: args) {
if(arg != null) {
key.append(arg.hashCode());
}
}
return key.toString();
}
Here’s the complete aspect.
/**
* Aspekt, který slouží jako cache pro DAO vrstvu. Vrácený objekt je cacheován v
* případě, že je vrácen z DAO vrstvy (leží v package cz.sweb.pichlik.aspectj.samples.dao a
* má v názvu suffix Dao) metodou začínající na find
* a nebo get.
*
*
Z hlediska klienta DAO vrstvy se vše chová transparentně, klient nepozná
* jestli došlo k dotazu na databázi a nebo jestli je objekt vrácen z cache. Jako
* cacheovací framework byl použit Java Caching System.
*/
public aspect CacheAspect {
private JCS cache; //instance cache manageru
/**
* Konstruktor aspektu, který inicializuje cache.
*
* @see CacheAspect#initCache()
*/
public CacheAspect() {
initCache();
}
/**
* Pointcut, který zareaguje na každé volání veřejné metody get*,
* nebo find* na objektu *Dao, který leží v package
* cz.sweb.pichlik.aspectj.samples.dao.
*
Pozn:
* vzor get* je potencionálně nebezpečný, protože by se nám mohl
* aplikovat i na gettry spadající do rodiny klasických accesoru, které nemají
* nic společného s CRUD metodami.
*/
pointcut getExecs():
call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.get*(..))
|| call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.find*(..));
/**
* Pointcut, který zareaguje na každé volání veřejné metody update*,
* dalete* nebo save* na objektu *Dao,
* který leží v package cz.sweb.pichlik.aspectj.samples.dao.
*/
pointcut deleteOrUpdateOrSaveExecs():
call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.update(..))
|| call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.delete(..))
|| call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.save(..));
/**
* Advice, který obaluje metody pro získáni objektu. V případě, že objekt
* ještě není cacheován, původní metoda se zavolá a její výsledek se uloží
* do cache. V případě, že je objekt cacheován, vrací se z cache.
*
* @return cacheovaný objekt
*/
Object around(): getExecs() {
String groupName = getGroupName(thisJoinPoint);
String cachedObjectKey = getCachedObjectKey(thisJoinPoint);
//zjistime jestli je objekt cacheovan
Object result = get(cachedObjectKey, groupName);
if(result == null) { //objekt neni v cache
result = proceed();//zavolame puvodni metodu
if(result != null) {
put(cachedObjectKey, groupName, result);//vysledek ulozime do cache
}
} else {
System.out.println("Objekt:" + result + " vrácen z cache");
}
return result;
}
/**
* Advice před vykonáním smazání, vytvoření nebo změny objektu, která invaliduje celou
* cacheovanou skupinu.
*/
before(): deleteOrUpdateOrSaveExecs() {
String groupName = getGroupName(thisJoinPoint);
invalidate(groupName);
}
/**
* Vrací jméno cacheovací skupiny. To je určeno na základě jména třídy.
* @param jp aktuální joinpoint
*/
protected String getGroupName(JoinPoint jp){
return jp.getStaticPart().getSignature().getDeclaringType().getName();
}
/**
* Vrací klíč pro cacheovaný objekt. Klíč je vypočítán na základě jména metody,
* která se volá a hodnot, které jsou předány jako argumenty (parametry)
* metody.
*
* @param jp aktuální joinpoint
*/
protected String getCachedObjectKey(JoinPoint jp){
String methodName = jp.getStaticPart().getSignature().getName();
StringBuilder key = new StringBuilder(methodName);
//z hodnot argumentu volane metody vytvorime klic cacheovaneho objektu
Object args[] = jp.getArgs();
for (Object arg: args) {
if(arg != null) {
key.append(arg.hashCode());
}
}
return key.toString();
}
//-----------------------------------------//
// Definice metod pro praci s cache //
//-----------------------------------------//
/**
* Provede inicializaci cache. Metoda je volána pouze jednou při vytvoření
* aspektu.
*/
protected void initCache(){
try{
this.cache = JCS.getInstance("");//získáme defaultní cache
} catch (CacheException e){
throw new RuntimeException(e);
}
}
/**
* Invaliduje všechny cacheované objekty v dané skupině.
* @param groupName jméno skupiny
*/
protected void invalidate(String groupName){
this.cache.invalidateGroup(groupName);
}
/**
* Odstraní objekt z dané cacheovací skupiny.
* @param cachedObjectKey klíč objektu, který bude odstraněn
* @param groupName jméno skupiny
*/
protected void remove(String cachedObjectKey, String groupName) {
this.cache.remove(cachedObjectKey, groupName);
}
/**
* Vloží objekt do dane cache skupiny.
*
* @param cachedObjectKey klíč objektu
* @param groupName jméno skupiny
* @param cachedObject objekt, který bude cacheován
*/
protected void put(String cachedObjectKey, String groupName, Object cachedObject){
try{
this.cache.putInGroup(cachedObjectKey, groupName, cachedObject);
}catch(CacheException e){
throw new RuntimeException(e);
}
}
/**
* Vrací objekt z dané cache skupiny.
* @param cachedObjectKey klíč objektu
* @param groupName jméno skupiny
* @return cacheovaný objekt, pokud ještě není cacheováný pak null
*/
protected Object get(String cachedObjectKey, String groupName) {
return this.cache.getFromGroup(cachedObjectKey, groupName);
}
}
To check that it works, we’ll write some simple code that calls the DAO.
public static void main(String[] args) {
FooDao dao = new FooDao();
FooBean bean = new FooBean(1);
dao.save(bean);
dao.findById(1); //puvodni metoda
dao.findById(1); //cache
dao.findById(1); //cache
dao.update(bean);//invalidace cache
dao.findById(1); //puvodni metoda
dao.findById(1); //cache
dao.delete(bean);//invalidace
dao.save(bean); //invalidace
dao.findById(1); //puvodni metoda
dao.findById(1); //cache
dao.save(bean); //invalidace
dao.getAll(); //puvodni metoda
dao.getAll(); //cache
dao.delete(bean);//invalidace
dao.getAll(); //původní metoda
}
To avoid bringing logging code into the DAO, we’ll write another aspect, this time for logging. Unlike the caching aspect, it defines the pointcut directly in the advice.
/**
* Logovací aspekt (no spíš trasovací ;-), který slouží k logování událostí DAO vrstvy. Narozdíl od
* CacheAspectu je pointcut nadefinován přímo v rámci advice
* a používá konstruktů within a execution. Rozdíl mezi
* execution a call je z hlediska toho jestli se zavěšení
* provede na volajícím nebo volaném objektu. V případě execution
* se zavěšení provede na volaném objektu, oproti call kde je to
* na volajícím.
*
* thisJoinPoint ({@link JoinPoint}) a thisJoinPointStaticPart
* {@link JoinPoint.StaticPart} jsou implicitní objekty každého aspektu.
*/
public aspect LoggingAspect {
before() : within(cz.sweb.pichlik.aspectj.samples.dao.*Dao) && execution(public * *(..)){
String clazzName =
thisJoinPoint.getStaticPart().getSignature().getDeclaringType().getName();
String methodName =
thisJoinPointStaticPart.getSignature().getName();
Object args[] = thisJoinPoint.getArgs();
StringBuilder sb = new StringBuilder("Volána metoda: ");
sb.append(clazzName);
sb.append(".");
sb.append(methodName);
sb.append("(");
for (int i = 0; i < args.length; i++) {
if(i != 0){
sb.append(",");
}
sb.append(args[i]);
}
sb.append(")");
System.out.println(sb.toString());
}
}
We also used execution instead of call (see the logging aspect’s Javadoc for the explanation) and within to specify which objects to log.
All that’s left is to unpack the output that appears when we run the program.
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.update(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.delete(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.getAll()
Objekt:[cz.sweb.pichlik.aspectj.samples.FooBean@20] vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.delete(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.getAll()Aspects add another dimension to programming. We’ve touched on only some of the things they can do. If AOP has caught your interest, take a look at the AspectJ documentation. It has a lot of useful information. I’ll finish with a description of the AJDT Eclipse plugin.
AJDT
The AJDT plugin (update URL for Eclipse 3.2.x http://download.eclipse.org/tools/ajdt/32/update) gives Eclipse an Aspect visualization perspective. It shows graphically where each aspect is applied.

Full size (about 125 KB)
Most of the time you’ll still write code in the regular Java perspective. AJDT extends its Outline view, as you can see at the upper right in the next image. Blue lightning bolts mark pointcuts, and arrows mark different kinds of advice.

Full size (about 142 KB)
The next screenshot also shows the Java perspective, this time while editing code where advice applies. Hovering over the advice symbol, with arrows for types such as before and around, shows which advice applies and in which aspect.

Full size (about 139 KB)
You can debug aspects like any other code.

Full size (about 125 KB)
Every API should have documentation. Project >> Generate AJdoc… creates Javadoc with information about aspects and where they apply.

Full size (about 54 KB)
Related articles
Related CZ Podcast episodes
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi and Filemon discuss Java 6 and web application frameworks. · Czech audio
- #1 · Vývojová prostředí v Javě
The first CZ Podcast episode looks at Java development environments. · Czech audio