« May 2004 | Main | July 2004 »
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 07:33 PM [permalink] | Comments (0)
June 22, 2004
Implementing caching with AspectJ - part I
Time for some code...
Often in talks we give examples of common aspects, such as caching, pooling, auditing, security, persistence, and so on. I thought I'd start a mini-series of blog entries that examine some of these common aspects in turn and show you how to implement them using AspectJ. Today I'm going to look at caching.
Before we can do any caching, we need something to cache. Here's a simple DataProvider
class that has a couple of expensive operations in its interface:
/** * @author adrian at aspectprogrammer.org * * A mock data providing class that can be used to illustrate caching * techniques using AspectJ. */ public class DataProvider { private int multiplicationFactor = 0; private int expensiveToComputeValue = 0; public DataProvider(int seed) { multiplicationFactor = seed; expensiveToComputeValue = seed; } /** * expensiveOperation is a true function (it always * returns the same output value for a given input * value), but takes a long time to compute the * answer. */ public int expensiveOperation(int x) { try { Thread.sleep(1000); } catch (InterruptedException ex) {} return x * multiplicationFactor; } /** * The expensive to compute value is different each * time you ask for it. It also takes a long time to * compute. */ public Integer getExpensiveToComputeValue(int x) { try { Thread.sleep(1000); } catch (InterruptedException ex) {} return new Integer(x + expensiveToComputeValue++); } }
Before going any further let's write a simple set of test cases that we want to pass when caching is working. The tests look to see that the cache offers a performance speed-up, and that the returned values from the cache are correct.
** * @author adrian at aspectprogrammer.org * Verify that the cache is doing its job. */ public class CachingTest extends TestCase { private DataProvider provider; public void testExpensiveOperationCache() { long start100 = System.currentTimeMillis(); int op100 = provider.expensiveOperation(100); long stop100 = System.currentTimeMillis(); long start200 = System.currentTimeMillis(); int op200 = provider.expensiveOperation(200); long stop200 = System.currentTimeMillis(); long start100v2 = System.currentTimeMillis(); int op100v2 = provider.expensiveOperation(100); long stop100v2 = System.currentTimeMillis(); long start200v2 = System.currentTimeMillis(); int op200v2 = provider.expensiveOperation(200); long stop200v2 = System.currentTimeMillis(); long expectedSpeedUp = 500; // expect at least 0.5s quicker with cache assertTrue("caching speeds up return (100)", ((stop100 - start100) - (stop100v2 - start100v2)) >= expectedSpeedUp); assertTrue("caching speeds up return (200)", ((stop200 - start200) - (stop200v2 - start200v2)) >= expectedSpeedUp); assertEquals("cache returns correct value(100)",op100,op100v2); assertEquals("cache returns correct value (200)",op200,op200v2); assertTrue("cache does not give erroneous hits",op200 != op100); } public void testExpensiveToComputeValueCache() { long start1 = System.currentTimeMillis(); Integer val1 = provider.getExpensiveToComputeValue(5); long stop1 = System.currentTimeMillis(); long start2 = System.currentTimeMillis(); Integer val2 = provider.getExpensiveToComputeValue(5); long stop2 = System.currentTimeMillis(); long expectedSpeedUp = 500; // expect at least 0.5s quicker with cache assertTrue("caching speeds up return", ((stop1 - start1) - (stop2 - start2)) >= expectedSpeedUp); assertEquals("get cached value rather than incremented one",val1,val2); } /* * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); provider = new DataProvider(100); } }
Now at last we're ready to introduce the first caching aspect. This is a straightforward aspect that hard-wires the cache implementation (such as it is), and the operations to be cached.
/** * @author adrian at aspectprogrammer.org * Illustrating the bare-bones of a caching implementation using AspectJ */ public aspect BogBasicHardWiredCache { private Map operationCache = new HashMap(); private Map expensiveToComputeValueCache = new HashMap(); pointcut expensiveOperation(int x) : execution(* DataProvider.expensiveOperation(int)) && args(x); /** * caching for expensive operation */ int around(int x) : expensiveOperation(x) { int ret = 0; Integer key = new Integer(x); if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(x); operationCache.put(key,new Integer(ret)); } return ret; } pointcut expensiveValueComputation(int x) : execution(* DataProvider.getExpensiveToComputeValue(int)) && args(x); /** * caching for expensive to compute value */ Integer around(int x) : expensiveValueComputation(x) { Integer ret = null; Integer key = new Integer(x); if (expensiveToComputeValueCache.containsKey(key)) { ret = (Integer) expensiveToComputeValueCache.get(key); } else { ret = proceed(x); expensiveToComputeValueCache.put(key,ret); } return ret; } }
The bog-basic hard-wired caching aspect keeps a separate cache (I've just used a HashMap, but a real implementation would use something more sophisticated) for the expensiveOperation
values and for the expensiveToComputeValues
. The actual caching is very simple. In both cases I use around advice to see if the cache has a value for the given key, and if so return it without executing the expensive method. If there is no value in the cache, I execute the method and then store the return value in the cache before giving it back to the caller. With this aspect in place, the test cases pass.
We can do better than this though. We probably want to make the actual cache implementation configurable, and we can capture the essence of the caching algorithm in a library aspect, which I've called org.aspectprogrammer.caching.SimpleCaching
:
/** * @author adrian at aspectprogrammer.org * A simple caching aspect with configurable cache implementation. * Sub-aspects simply specify the cachedOperation pointcut. */ public abstract aspect SimpleCaching { private Map cache; /** * Use the Map interface as a good approximation to a * cache for this example. * The cache can be provided to the aspect via e.g. dependency * injection. */ public void setCache(Map cache) { this.cache = cache; } /** * We don't know what to cache (that's why this is an abstract * aspect), but we know we need a key to index the cached values * by. */ abstract pointcut cachedOperation(Object key); /** * This advice implements the actual caching of values and * cache lookups. */ Object around(Object key) : cachedOperation(key) { Object ret; if (cache.containsKey(key)) { ret = cache.get(key); } else { ret = proceed(key); cache.put(key,ret); } return ret; } }
This aspect assumes that dependency-injection of some kind (perhaps configuration via Spring as I've illustrated in previous posts) will be used to pass the aspect a cache implementation (I've been lazy and just used Map as the cache interface here). The aspect is abstract as it doesn't know what to cache (the cachedOperation
pointcut is abstract), but it does know how to implement the cache-lookup and population algorithm.
To make the test cases pass using this library aspect we need to introduce a couple of concrete sub-aspects. Here's the caching aspect for the expensiveOperation
:
/** * @author adrian at aspectprogrammer.org * Illustrating use of the SimpleCaching library aspect, * with the cache at the data provider. */ public aspect ExpensiveOperationCaching extends SimpleCaching { pointcut cachedOperation(Object key) : execution(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 ExpensiveOperationCaching() { setCache(new java.util.HashMap()); } }
Because I used an "execution" pcd to define the cachedOperation pointcut, this cache will be on
the "execution" (ie server) side of the expensiveOperation
.
Here's the aspect that caches the expensive-to-compute value. This aspect uses a "call" pcd to define the cachedOperation pointcut, and so this cache will be on the "call" (client) side - possibly an important distinction if the operation is a remote call.
/** * @author adrian at aspectprogrammer.org * Illustrating use of the SimpleCaching library aspect, * with the cache on the client side (useful if e.g. the * data provider is remote). */ public aspect ExpensiveToComputeValueCaching extends SimpleCaching { 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 ExpensiveToComputeValueCaching() { setCache(new java.util.HashMap()); } }
Notice how easy it is to reuse the library aspect? I think that's enough for one day. Tomorrow I'll show you some easy extensions to get per-client caches (each client has a seperate cache), per data-provider caches (when there are multiple distinct data providers), and even per-transaction caches (with a lower-case "t") that give a constant value for the duration of a transaction.
Posted by adrian at 09:20 PM [permalink] | Comments (2)
June 21, 2004
Using Eclipse 2.1, but want to use the latest AspectJ compiler?
If you're using Eclipse 2.1 (perhaps because you're working with WebSphere Studio Application Developer, or you just haven't been keeping up with the stream of Eclipse 3.0 releases - RC3 was out this weekend btw.) then until now you've been stuck with the 1.1.1 version of the Aspectj compiler that is bundled with AJDT 1.1.4/1.1.6. Andy (Clement) just released a set of scripts that can be used to update the AspectJ compiler versions used by those releases, so you can now upgrade to the 1.2 compiler.
In related news, Carlos Sanchez announced version 3.1 of the Maven AspectJ plugin today.
Posted by adrian at 05:20 PM [permalink] | Comments (0)
June 19, 2004
"Go From" Statement Considered Harmful
I was thinking this morning about the best way to explain the rationale for the set of join points exposed by AspectJ's join point model. Join points are events that occur during the run-time execution of a program. There are many such events: the static initialization of a class, a call to a method, the execution of the 2011th bytecode instruction, and so on. Why are the first two of these exposed as join points, but the third is not?
AspectJ's join points are described in the programming guide as "principled" points in the runtime execution of a program. Certainly I hope it is clear that the execution of a method is a more signifcant (and stable in the face of program evolution) event than the execution of an arbitrary line of code. As I was thinking about this, I hit upon the following analogy:
Ever since Dijkstra's famous paper "Go To Statement Considered Harmful," programmers have been wary of using the goto statement, to the point that it is now virtually extinct. Jumping to arbitrary points in the program flow using a goto makes it very hard to understand what's going on. As Dijkstra put it, "The go to statement as it stands is just too primitive; it is too much an invitation to make a mess of one's program." In place of the goto Dijkstra recommends more structured alternatives for specifying program control flow - including if-then-else statements, looping constructs, and procedure calls.
Allowing join points for arbitrary events in a program's execution (and providing a corresponding pointcut designator to match them) would be the aspect-oriented equivalent of the goto statement - the "go from" statement perhaps? In the spirit of Dijkstra's paper then, we can argue "Go From Statement Considered Harmful." AspectJ doesn't provide join points for arbitrary points in the program execution because they would be "just too primitive; too much an invitation to make a mess of one's program." Instead AspectJ provides join points for more structured events in the program control flow - method calls and executions, class and object initialization, and so on. The aim of AspectJ is to help you write clearer, simpler, more direct programs, not spaghetti.
Posted by adrian at 03:03 PM [permalink] | Comments (1)
June 18, 2004
AspectJ on the road...
I've just got back from a European mini-tour, presenting on AspectJ at the Belgium JUG and at the IBM WebSphere User Group meeting. Both events were great fun, the Belgium JUG especially was amazing - a packed room of well over 150 attendees all eager to hear about AOP. They're a great crowd and if you ever get a chance to go and speak there I thoroughly recommend it. I presented 2 hours of tutorial material and demonstrations, which the JUG team have videod so at some point I believe you'll be able to see the presentation from the comfort of your web browser.
My talk was in two parts - the first part was an introduction to the ideas and motivation for aspect-oriented programming, and a look at how they are embodied in AspectJ. We managed to get full-audience participation for the explanation of join points and pointcuts (I guess you had to be there...) which was a lot of fun. The second part of the talk focused on adopting AspectJ - the recommend phases of adoption and some examples of aspects you might be writing in each phase.
The slides used a number of builds and animations, and we also spent quite a lot of time looking at code in AJDT. Neither of these can be reproduced in a PDF of the slides (nor my general silliness and hand-waving, so there are some benefits ;) ), but I'm making them available on the aspectprogrammer.org site in any case for anyone who is interested. You can find the PDFs in the ""Introduction and Tutorials" section under "Articles and Resources". If you want to see the whole show, you'll just have to invite me to come and give a talk ;).
In a piece of unrelated news, I believe I have now fixed the problem whereby blog entries didn't appear in the browser window if it was too narrow (thanks to all those folks who pointed this out), and I've also changed the format of the news feed for this blog so that the full text of entries is sent (which is how it used to work on jroller) rather than just the first couple of sentences. I opened up the commenting so that you don't have to register with typekey first just to post here, but I'm still having teething problems getting the typekey registration to work for those who do have typekey accounts. Bear with me...
Posted by adrian at 10:52 AM [permalink] | Comments (3)
June 15, 2004
aspectprogrammer.org main site now open
I just flicked the switch and opened up the main site pages for aspectprogrammer.org. If you go there now, you'll basically find articles from the blog indexed by category, with skeletal news, faq and web links pages (I haven't populated those yet). There are also a few articles and papers that haven't been featured in the blog.
In the Under the hood section I've placed a copy of Jim Hugunin's "Guide for the developers of AspectJ's Compiler and Weaver." This has been in the AspectJ CVS tree for a while now, but is more accessible in its new form. An interesting read if you're curious about what goes on inside the AspectJ compiler.
In the Academic section you'll find a collection of papers relating to, amongst other things, our use of AspectJ inside IBM - the most recent paper there is "Large Scale AOSD for Middleware."
Posted by adrian at 07:39 AM [permalink] | Comments (1)
June 14, 2004
Subscribing to this blog...
A lesson in the pain of running your own site. I use bloglines to subscribe to various feeds. I noticed after my last post that it wasn't correctly picking up the new entry. Seems it doesn't get on all that well with the RDF feed (RSS 1.0), but is much happier when pointed to this index file (RSS 2.0) : http://www.aspectprogrammer.org/blogs/adrian/index.xml. If your subscription client is having trouble picking up new entries in this blog, I recommend you switch too. I've changed all the links on the site to point to the XML index file by default now.
Posted by adrian at 09:13 PM [permalink] | Comments (0)
Books on AspectJ
Someone sent me a link to a book at the Barnes and Noble online bookstore the other day. I don't normally use the Barnes & Noble site (I started with Amazon years ago and I've just stuck with them - are others better? I don't know, Amazon works well enough for me... there's a lesson in first mover advantage for you). Anyway, whilst I was on the site I thought I'd see what AspectJ books they were offering.
You can try this at home, just go to http://www.barnesandnoble.com/ and type in "AspectJ" in the search dialog that appears on the home page. Apparently they stock 131,173 titles on AspectJ, which are presented to the user sorted by "Top Matches." So what are Barnes & Nobles top ten books on AspectJ then? In descending order:
- Founding Mothers: The Women who Raised our Nation
- Tuesday with Morrie: An old man, a young man, and life's great lesson
- The Americanization of Benjamin Franklin
- Plan of Attack
- Rich Dad, Poor Dad: What the Rich teach their kids about monoy - that the poor and middle class do not
- On the Down Low: A Journey Into the Lives of Straight Black Men Who Sleep With Men
- Superfoods Rx: Fourteen Foods That Will Change Your Life
- Pledged: The Secret Life of Sororities
- Ultimate Weight Solution: The 7 Keys to Weight Loss Freedom
- The Ultimate Weight Solution Food Guide
How useless is that??! So, the next time I'm asked to recommend a book on AspectJ, it will just have to be "Founding Mothers: The Women who Raised our Nation" - the reviewers on the B&N site certainly seem to like it anyway!
Somebody did ask me to recommend an AspectJ book today, and over on my trusty amazon.co.uk site I got quite a shock.
I'm writing a book on "Aspect Oriented Programming with AspectJ and the Eclipse AspectJ Development Tools" (quite a mouthful, which is why the publisher has given it the short title of "eclipse AspectJ." My co-authors are members of the aspects team here in Hursley - Andy Clement, George Harley, and Matthew Webster. It wasn't the fact that I'm writing a book that gave me a shock (believe me, I know all about that), it was the fact that it now appears on the amazon web site, with a cover photo too, and we had absolutely no idea. Nor had we ever seen the cover of the book before.
So now you know. We're writing a book on AspectJ and AJDT, which is due out later this year, and the cover looks like this:
Posted by adrian at 06:46 PM [permalink] | Comments (0)
June 11, 2004
I've moved...
You may have noticed that the jroller site have been having a lot of problems this last week or so. Well, you get what you pay for (and jroller is free), so I have no rights to complain. However, the troubles made me realise that I wanted a bit more control over my site, and a bit more predictability, so I've moved home. Since you're reading this, you've obviously found me: the new URL for the Aspects Blog is : http://www.aspectprogrammer.org/blogs/adrian.
Having more control of the site will let me arrange the material in sections too for easy browsing. That piece of the site isn't quite ready yet, but I hope to have it online soon.
Once everything is up and running, I hope that I can back to writing blog entries, rather than playing around with website administration...
Posted by adrian at 02:35 PM [permalink] | Comments (0)
June 04, 2004
Making Programs Simpler
This isn't what I originally intended to write about today - I was planning to write about Spring again, or about the crop of testimonials currently appearing on the AspectJ user's list - but there's an interesting debate going on 'back at the office' this afternoon and I thought I'd share some of that with you instead.
The central issue of the debate is whether aspect-oriented programming can make a meaningful contribution to tackling the complexities of software development, or whether the impact that can be made there is in fact relatively uninteresting in the grand scheme of things. To put in another way, do the inherent complexities in any real system of size dwarf into insignificance the complexity issues that the ideas of AOP can help address? Guess which side I'm arguing ;). Just to be clear, the other parties in the debate are AO supporters too, they just see the ideas of aspect orientation having an even more profound impact at the high-level design and architecture levels. I agree that AO can have a greater impact at the design and architecture levels (in the same way that I agree that any idea can have a greater impact in architecture than in design than in code simply because the implications of decisions made there are so much greater), but I still want aspect-oriented programming too.
So, is AOP useful or is it just fiddling around the edges of an already-too-complex set of programming languages and technologies? Let me put the case, and I'll leave you to make up your own mind (though I'm always interested to hear your opinions on the topic).
I want you to consider three programs, each program addresses the same requirement (so the inherent complexity in the problem statement is the same in every case), but the programs meet the requirement in different ways.
Here's the first program:
public class HelloWorld { static int[] char_data = new int[] {104,101,108,108,111,32,119,111,114,108,100}; public static void main(String[] args) { for (int i = 0; i < char_data.length; i++) { System.out.print((char)char_data[i]); } System.out.println(); } }
If you run this program you'll see the output:
hello world
Here's the second program:
public class HelloWorld { public static void main(String[] args) { System.out.println("hello world"); } }
If you run this program you'll see the output:
hello world
Finally, here's the third program:
print 'hello world'
If you run this program you'll see the output:
hello world
As a programmer, I think the second program is a significant improvement over the first program, and in turn the third program is a significant improvement over the second program. Moreover, I think those improvements do make a real difference to the ease of understanding, maintaining, and creating them.
But what on earth have these programs got to do with AOP???
When I wrote the first program, I put myself under the restriction that I couldn't use the abstraction 'String' (and nor 'character', as best I could). The difference between program 1 and program 2 illustrates the difference between the case when a programming language can directly support an abstraction useful to solving a problem and when it can't. Now think about the model-view-controller example again that I introduced in "AOP without the buzzwords." In plain Java, I can't directly represent the abstraction 'view notification,' so I have to code it long-hand with maybe a listener interface, some methods to add and remove listeners, and a bunch of code to notify listeners when state changes. In AspectJ, I can directly represent the abstraction ViewNotification. In fact, if I'm being pragmatic / agile / pick-your-favourite buzzword, I won't even bother with creating the full listener interface and registration machinery first time round - if i've just got a single model and a single view class, I'll simply encode the wiring directly. When you've got the ability to create a suitable abstraction, you can do things like that.
public aspect ViewNotification { private List Model.viewers = new ArrayList(); pointcut viewCreation(View v,Model m) : execution(View.new(Model)) && this(v) && args(m); pointcut viewDisposal(View v) : execution(* View.dispose(..)) && this(v); after(View v, Model m) returning : viewCreation(v,m) { m.viewers.add(v); } after(View v) returning : viewDisposal(v) { v.getModel().viewers.remove(v); // train wreck! } // one simple way of defining state change... pointcut modelStateChange(Model m) : set(!transient * Model.*) && target(m); after(Model m) returning : modelStateChange(m) { for (Iterator it = m.viewers.iterator(); it.hasNext(); ) { View v = (View) it.next(); v.modelUpdate(m); } } }
The ability to create (and even better to reuse from a library) meaningful abstractions is the difference between program 1 and program 2, and it's a difference that AOP can make for a non-trivial subset of problems.
I made a different kind of shift when I moved from the second hello world program to the third. (And no, the moral of the story is not 'just use perl!'). Instead of creating an appropriate abstraction, I changed my tool. I moved to a programming language (perl, but I bet the exact same source could be a valid program in a few other programming languages too) that allowed me to meet the requirement directly, without any baggage of 'stuff' there to keep the language happy, or to support the output statement. When you look at it this way, the Java program really does require a whole bunch of stuff that just gets in the way for solving this problem and adds no real value.
But what's that got to do with AOP either?
In "AOP without the buzzwords" I talked about concept:implementation ratios. The one ratio I didn't discuss was the one we see here (the article was getting too long already): a concept:implementation ration of 0:1. Is there such a thing? Sure there is - it happens when you get artefacts in the implementation that aren't there to directly represent any concept or idea in the design / requirements space (any time you deviate from 1:1 things start to get interesting). By creating directly usable abstractions, AOP can help us eliminate some of the implementation details that otherwise are there simply to support other pieces of the implementation. There's no requirement in the design that there be a listener interface, a registration mechanism, or an Observable model class. These are implementation artefacts we create to support the true requirement of view notification. In an AOP language, sometimes we can get away without those supporting constructs, and just write what we mean directly.
So, by adding abstraction and removing implementation clutter, we made a significant improvement from program 1 to program 3. AOP languages like AspectJ support both of those kinds of improvements in program structure too. I think the difference is worth it, and from the testimonials on the user lists, so do a number of others. What about you?
Posted by adrian at 12:13 PM [permalink] | Comments (1)
June 02, 2004
Fancy a weekend in Vancouver? (and learn about AOP too...)
I've never been to Vancouver before, but I hear it's a great place and I'm looking forward to visiting. I'll be there this September, 18th and 19th, to help teach an "Intensive Course on Aspect-Oriented Programming. I'm really excited about the course for a number of reasons :-
- this is the first time that we've been able to dedicate two full days to teaching AOP (the beginning and advanced AspectJ tutorials that we've given before together only add up to a day)
- there's a lot of new material that we'll get a chance to cover
- we're not going to be focusing exclusively on AspectJ - teaching AOP is more important than any one dialect
- and, although I've co-presented with Mik Kersten several times before, I've never yet had the chance to co-present with Gregor and Gail - and I think that's going to be a lot of fun.
The agenda was published today, but registration won't be open until next month. I really hope you can join us, it promises to be a great weekend.
In the next couple of weeks you can also catch me at:
- The Belgium JUG event in Brussels on Wednesday 16th June
- The UK WebSphere User's Group meeting in Edinburgh on Thursday 17th June (agenda yet to be updated on their website to include this session).
Rod Johnson will also be at both of those events talking about Spring and the Spring AOP framework. Which reminds me, I must get back to writing more about using Spring and AspectJ together (part 1, part 2) soon...
Posted by adrian at 03:26 PM [permalink] | Comments (1)
June 01, 2004
AspectJ Hacks - #1 From System.out.println to switchable debug output
As an antidote to yesterday's entry, today I thought I'd just show you a simple basic hack. This isn't great coding style, and you wouldn't want to leave it in your source code for long, but it's a damn useful short-term tactic some times.
I was working in a test class that kicked off lots of asynchronous background operations. The background operations took a ProgressMonitor argument, and called done() on the ProgressMonitor when they had completed. Because this was part of a test case, I needed to block until the asynchronous operations had completed so that I could verify their results. To cut a long story short, I had some wait-and-notify stuff going on, and a whole lot of println() statements littered throughout the code to help me make sure I'd got it right (yes, I confess, I do still sometimes use System.out.printlns when I'm debugging). I now wanted to get rid of all the println statements so that the test case could run without printing lots of unwanted output to the console. Rather than delete all of the statements, or comment them out, I used an aspect to control their execution via a debug flag.
Let's pretend that the following handy class actually does something useful - it's certainly sufficient to illustrate the technique. The real class I was working with had a lot more printlns in it than this.
public class AHandyClass { public static void main(String[] args) { AHandyClass handy = new AHandyClass(); handy.doSomeComplexFunction(); } public AHandyClass() { System.out.println("Making a handy object for you"); } public void doSomeComplexFunction() { System.out.println("I'm doing the complex bit now..."); } }
Instead of deleting (I knew I'd need them again soon) or commenting out (too tedious with so many entries littered around) the println calls, I added a simple inner-aspect to the class:
public class AHandyClass { private static aspect Debug { public static final boolean debugEnabled = false; void around() : call(* PrintStream.print*(..)) && within(AHandyClass) { if (debugEnabled) proceed(); } } public static void main(String[] args) { AHandyClass handy = new AHandyClass(); handy.doSomeComplexFunction(); } public AHandyClass() { System.out.println("Making a handy object for you"); } public void doSomeComplexFunction() { System.out.println("I'm doing the complex bit now..."); } }
This works by using around advice on all the calls to print* methods on a PrintStream. The around advice only calls proceed if the debugEnabled flag is set to true, so when debug is turned off none of the calls to println get a chance to execute. The "&& within(AHandyClass)" portion of the pointcut is important as it limits the debug control to just this one class. Normally I wouldn't use around advice to simply 'remove' an unwanted call, but as a working hack it's quick and effective in this case.
It's a very short and simple program - why not cut-and-paste the source into your editor and have a play with it using the AspectJ compiler, or better still, use AJDT?
Posted by adrian at 03:23 PM [permalink] | Comments (0)