Wednesday, April 26, 2006

Gambling on AspectJ

Squeezed in with everything else that's going on (the end of one client project and a lot of personal and business travel), I've been working on the start of the code base that will be Tapestry 5.

I think it is going to be a monster. In a good way. More on that later.

Tapestry 5 is targetted at JDK 1.5 and above. That means annotations, and not XML, will be the rule of the day. JDK 1.5 also means the use of Doug Lea's concurrency support, now java.util.concurrent.

Further, this is a chance for me to dip my toes in the Aspect Oriented pool.

I can picture the reaction: "Howard, that's the wrong 'A'! AOP is so ... 2004? Shouldn't you be focused on Ajax?"

However, the AOP support actually aligns nicely with much that I've done with HiveMind, using Javassist. It's been interesting to see the kind of things you can do statically, at build time, using AspectJ and how they compare to what you can do dynamically, at runtime, using HiveMind. Each approach has merit in different circumstances, and I'm trying to find out exactly where the lines of demarkation are.

For example, I've been doing a lot of defensive programming. So I've been creating aspects to help with that. It's been relatively easy to write aspects that inserts null check code into each non-private method. I like the don't accept null, don't return null coding approach, and my aspect allows me to ensure that all my code works that way. Further, for the rare cases where I want to allow null, I can place a @SuppressNullCheck annotation on a class or individual method.

I could do the same thing using HiveMind ... but I'd really be restricted to applying it to HiveMind services, since it would take the form of an interceptor. Tapestry tends to have lots of data and modeling objects in memory, that would benefit from these checks as much, or more so, than the service code. AspectJ is a better fit there.

I'm also doing some interesting work to use annotations to control concurrent read/write access to my classes.

I'm finding the mix of aspects and annotations to be very powerful. I use type annotations to narrow the set of classes to be affected by the aspect, and use method annotations to include or exclude individual methods. In my case, I have a @Synchronized annotation that indicates a class can have some of its method synchronized, and I have @Synchronized.Read and @Synchronized.Write to control which methods require a read lock, and which require the exclusive write lock.

I'm pretty happy from the point of view of getting more functionality (and, more robust functionality) from less code. Aspects are another approach to doing things that I normally associate with components and layering. The gamble part is how I'll feel once I'm trying debug into and through AspectJ-generated code. It becomes a bit tricky to figure out exactly what's going on once you've let AspectJ work things over.

I've also found that AspectJ really wants to work at the method level, when some of the things I want to do (such as validating individual parameters) are done at the individual parameter level. For example, I'd like to create method advice as the combination of a particular method and a particular non-primitive parameter of that method ... but the best I can do is advise the entire method and recieve all the parameters as an object array (whether they are primitive or not).

I have found the AJDT plugins for Eclipse to be a slight bit creeky. You definately want to upgrade your Eclipse to 3.1.2 (the latest and greatest). The visualization is great (when it works), as is the visual annotations of advised methods. There are numerous annoyances when editting aspects and browsing source, and I often have to perform clean builds of my code, something I hardly ever do when not using AJDT.

6 comments:

  1. Anonymous3:18 AM

    Could you please detail how could the concurrency support from 1.5 help in a webfamework?
    This sounds extremly interesting (I liked Doug Lea's book very much), but I simply can't imagine where would that fit in a web conext.

    Thank you,

    Bill.

    ReplyDelete
  2. Tapestry 4 uses a number of object pools and caches. Today, those objects are often in synchronized blocks to guard against the cases where the cache or pool needs to be updated.

    In JDK 1.4, the only real protection there was synchronized methods, meaning one thread at a time, other threads get to block and wait. This occurs even in the likely case that most of the threads are performing read-only behaviors. So there's a lot of time where threads are blocked needlessly ... leading to reduced throughput.

    Using real concurrency, you'll have fewer threads waiting, which will speed up throughput and overall performance.

    A web application is a great place to make use of concurrency since it is such a multi-threaded environment. We'll also have to see about a future HiveMind that makes use of the same features (fortunately, HiveMind is generally only worried about synchronization until a service is fully instantiated).

    ReplyDelete
  3. Anonymous3:44 AM

    Thank you for the details Howard.
    I somehow supposed that web based applications (and frameworks) may not mess up with threads et. co. (If I reacall well this was in some early books about Servlets , and the specifications).
    It seems that I must review what I've learnd a few years ago :).

    Bill.

    ReplyDelete
  4. Anonymous3:12 PM

    This comment on AspectJ...
    --------
    the best I can do is advise the entire method and recieve all the parameters as an object array (whether they are primitive or not)
    --------
    ... suggests you're not binding the method parameter in your advice - you know you can, e.g.,

    before(String[] args) : execution(void main(String[])) && args(args) { ...do something ... }
    // yes, AspectJ distinguishes the two uses of "args"

    or to pick out just one:

    void foo(int i, Object o, String s) { ... }

    pointcut sfoo(String s) :
    execution(void foo(int, Object, String) && args(int, Object, s);

    ReplyDelete
  5. In my case, I don't know the name of the method, or the number or type of parameters, which is limiting what I can accomplish.

    ReplyDelete
  6. Anonymous9:14 AM

    hmm,

    what if we write our reusable API in Clojure and use them in AspectJ ?

    ReplyDelete

Please note that this is not a support forum for Tapestry. Requests for help will be deleted. Please subscribe to the Tapestry user mailing list if you are in need of support, or contact me directly for professional (for pay) support.

Spammers: Don't bother. I delete your comments and it's a waste of time for both of us. 垃圾邮件发送者:不要打扰。我删除您的评论和它的时间对我们双方的浪费