« Implementing caching with AspectJ - part I | Main | I'm still here, Bill Gates likes AOP, and other stories... »

June 23, 2004

Implementing caching with AspectJ - part II

I promised yesterday in Implementing caching with AspectJ - part I to show some variants on the basic caching scheme I showed. We are already able to introduce a simple client-side or server-side cache for an operation by reusing the org.aspectprogrammer.caching.SimpleCaching aspect. Today I'll show you how to create per-client, per-data source, and per-transaction caches with very little effort too.

Here's how you would specify a per-client cache. What we're after here is that each client has their own private cache (perhaps the returned values are in some way particular to the client):

/** * @author adrian at aspectprogrammer.org * Illustrates use of SimpleCaching library aspect to implement a * (client-side) per-client cache. */ public aspect IndividualClientExpensiveOperationCaching extends SimpleCaching perthis(cachedOperation(Object)) { pointcut cachedOperation(Object key) : call(int DataProvider.expensiveOperation(int)) && args(key); /** * this constructor is here simply to facilitate testing all * of the different cache implementations without having the * test case depend on any one. Normally this dependency would * be set from outside of the aspect (e.g. by Spring). */ public IndividualClientExpensiveOperationCaching() { setCache(new java.util.HashMap()); } }

The key difference in this aspect is that we specified that a unique aspect instance should be created perthis(cachedOperation(Object). This means that we get one caching aspect for each unique object that calls the DataProvider. In other words, one cache per client.

In a similar vein, we may have multiple instances of the DataProvider, and want to provide a per-data provider cache (perhaps each data provider returns different values). This is easily achieved with a pertarget clause:

/** * @author adrian at aspectprogrammer.org * Illustrates use of the SimpleCaching library aspect to provide * a client-side data-provider specific cache. * (Use execution in place of call in the cachedOperation pcut definition * for a server-side cache). */ public aspect DataProviderSpecificExpensiveOperationCaching extends SimpleCaching pertarget(cachedOperation(Object)){ pointcut cachedOperation(Object key) : call(int DataProvider.expensiveOperation(int)) && args(key); /** * this constructor is here simply to facilitate testing all * of the different cache implementations without having the * test case depend on any one. Normally this dependency would * be set from outside of the aspect (e.g. by Spring). */ public DataProviderSpecificExpensiveOperationCaching() { setCache(new java.util.HashMap()); } }

This creates a new caching aspect instance for each unique target of a call to the DataProvider. In other words, for each DataProvider instance.

The final variant I will discuss centers around caching the getExpensiveToComputeValue return value within a transaction. I don't mean transaction in the JTA sense, simply the control flow of a given method. Recall that the implementation of getExpensiveToComputeValue returns a different answer each time it is called. If the per-transaction cache is working correctly, then within a single transaction (control-flow), the client wil see a constant return value no matter how many times the operation is called. Once the transaction is over, clients will see an updated value the next time the operation is called. A sample test case might make this clearer...

Here's a client that gets the expensive value a couple of times in the control flow of the getExpensiveValues method.

public class ProviderClient { DataProvider provider; public ProviderClient(DataProvider p) { this.provider = p; } public Integer[] getExpensiveValues(int x) { Integer a = getIt(x); Integer b = getIt(x); return new Integer[] {a,b}; } private Integer getIt(int x) { return provider.getExpensiveToComputeValue(x); } }

Now, here's the test case that we want to pass when the caching is working correctly:

public class PerTransactionCachingTest extends TestCase { public void testCaching() { ProviderClient c = new ProviderClient(new DataProvider(100)); Integer[] firstTime = c.getExpensiveValues(4); Integer[] secondTime = c.getExpensiveValues(4); assertEquals("cached in tx",firstTime[0],firstTime[1]); assertEquals("cached in tx",secondTime[0],secondTime[1]); assertTrue("not cached across tx", firstTime[0].intValue() != secondTime[0].intValue()); } }

Finally, here's the simple caching aspect that implements this policy:

/** * @author adrian at aspectprogrammer.org * Illustrates use of SimpleCaching library aspect to give a * cache that returns a constant value for the duration of a * given "transaction" (method execution control flow). */ public aspect PerTransactionExpensiveToComputeValueCaching extends SimpleCaching percflow(transaction()){ pointcut transaction() : execution(* ProviderClient.getExpensiveValues(int)); pointcut cachedOperation(Object key) : call(Integer DataProvider.getExpensiveToComputeValue(int)) && args(key); /** * this constructor is here simply to facilitate testing all * of the different cache implementations without having the * test case depend on any one. Normally this dependency would * be set from outside of the aspect (e.g. by Spring). */ public PerTransactionExpensiveToComputeValueCaching() { setCache(new java.util.HashMap()); } }

Notice that the aspect is specified "percflow," meaning that we get a unique aspect instance for every control flow beginning at a join point matched by the transaction pointcut. This is defined to be simply whenever we execute the getExpensiveValues method in the ProviderClient. With this aspect in place the test passes.

I wrote all the test cases and aspects for part I and part II of these blog entries using AJDT (what else :) ? ), and have a "caching" Eclipse project that contains all of the code. I'll zip this up and make it available in the "Code Samples" section of the aspectprogrammer.org website soon.

Posted by adrian at June 23, 2004 07:33 PM [permalink]

Comments

Post a comment

Thanks for signing in, . Now you can comment. (sign out)

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)


Remember me?