Thursday, 1 March 2007
Removing AOP magic with Java Closures and Java Closure method assignment
« Meldware joins Java.net | Main | Fun with Apple's part III »So I love VMs and I don't think Java is a great language (it is an "OK" language), but the JVM is the most productionable VM to date! Java doesn't make your mail server slow, your storage strategy makes your mail server slow :-). You will live or die by IO, not the few extra microseconds in Java's type system or the time between not-JIT and JIT or 1-10 line to instruction ratios or anything that the uninformed would complain about...Though it will suck air if you invoke the RMI subsystem anywhere and don't do this. Most professional C programmers use memory pools rather than malloc and free because they recognize that they are not indeed infalliable. Most of today's professional C programmers also use a sort of home-grown type system. So we're really not even talking about garbage collection or typing we're talking magic pixy dust (the same ASM out of the C compiler is just FASTER than the same ASM from Hotspot) or "load time". Don't "be a man"...just grow up and get some facts. I'd rather have a spot for you next to one of the world's best software developers and not at the kiddy table. But I digress...
I don't really love AOP very much (and if you aren't an AOP/EJB buff bear with me, this isn't REALLY about either, I'm using this as a demonstration for some candy I'd like to eat). I regard it as a lot of academic BS wrapped around some dirty hacks for what Java should do anyhow. You do hear about AOP in other languages but they're mostly talking about the runtime system to apply advice selectively (complete with academic gobbledy-gook language meant to complicate simple things), but not even 1/10th as much as in Java. They just don't need it as much.
Java's static typing coupled with missing features are why so much Majick is needed. The first things is closures. Closures as specified for JDK 7 more or less let you extend the language as follows:
public Currency transferMoney(Account from, Account to, Currency amount) {
Currency transferred = null;
transaction.required {
Currency withdrew = withdraw(from, amount);
Currency deposited = deposit(to, amount);
if (!withdrew.equals(deposited)) {
throw new CurrencyException("The amount withdrawn did not equal the amount deposited in transfer money (amount,withdrew,deposited)", amount, withdrew, deposited);
}
transfered = new Currency(amount); //copy constructor
}
return transferred;
}
In EJB3, you would do this with the @TransactionAttribute(TransactionAttributeType.REQUIRED) annotation. I would argue that in many cases closures are more appropriate. On one hand pervasive imperatives such as is required with "Bean Managed Transactions" in EJB and JTA is horrible. BTW Ruby on Rails's transaction support looks more like BMT in EJB/JTA than any of the above ;-). On the other hand EJB transaction tags are rather limited too (as described below).
The weakness of closures is that a lot of things really do have inherent transactional natures. Closures capture the above case where depending on the transaction you might do a withdrawl REGARDLESS of a deposit, but in this case you want them both in the same transaction. Doing this right means doing it in a way that is both simple and flexible. However, *not* having a default transactional nature is dangerous. Developers may forget something especially if 99% of the time it is repetitive template code (indeed EJB defaults to "REQUIRED"). A good API, language or whatever is designed for use by fallible programmers to use.
With EJB3+ this isn't a big deal. Since EJB3 maintains the same calling semantics. If I did this:
SomeEjb {
@TransactionAttribute(TransactionAttributeType(TransactionAttributeType.REQUIRES_NEW)
public void requiresNew() {...}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void required() {
doStuff();
this.requiresNew();
doOtherStuff();
}
}
and called "required", I would ONLY have one transaction and not the expected 2. EJB3 maintains the call by proxy semantics of EJB2. Meaning if I want the transactional nature of the method I must get the local or remote interface and not just do a Java-to-Java call. This is where the legacy of EJB2 infects EJB3. It isn't intuitive even though you may understand that IBM isn't going to stick byte code manipulation in WebSphere. You can solve this unintuitiveness by using something like JBossAOP @Tx(TxType.REQUIRED) annotation that actually does require/use bytecode manipulation:
SomeEjb {
@Tx(TxType.REQUIRES_NEW)
public void requiresNew() {...}
@Tx(TxType.REQUIRED)
public void required() {
doStuff();
this.requiresNew();
doOtherStuff();
}
}
Now I get two transactions because I either used JBoss's bytecode weaving at deploy time or I used "aopc". However, aren't you uncomfortable with bytecode manipulation in the classloader or a post compilation process in your build that forever ties each build to that version of the appserver or JBossAOP? I know I am. On the other hand a pre-compilation process sucks even more.
The closure version (with "inherent transactional nature") might look like this:
SomeEjb {
public void requiresNew() {
Transaction.requiresNew {
...
}
}
public void required() {
Transaction.required {
doStuff();
this.requiresNew();
doOtherStuff();
}
}
}
But boy it is easy to screw that up huh? And worse the caller can't override it. Maybe the original implementor thinks it has an inherent transactional nature and maybe 99.9% of the time it does. Ideally I want both annotations AND closures but NO bytecode magic. The cool thing is that if you're happy with the special proxy calling rule, then all you need to do is add it to your InvocationHandler or in the container code itself (WebSphere doesn't use dynamic proxies) and you're done!
However I'm not happy with the special proxy calling rule are you? That's cruddy and inherently confusing. What we need to round this out is a way for the deployer to assign a closure to a method. Meaning it would reflect on the bean, see that it had an annotation, then assign a closure to the method. "whenever you see this, then assign this closure to it".
Class clazz = ...;
Object bean = ...;
Method method = ...;
if (methodHasTransactionRequired(method)) {
method.setClosure(bean, required);
}
Meaning whenever that method is called on that bean on that instance...wrap it in the closure. However there can be only one closure in the above code. Really we want to allow at least 32767 of them, give or take. So we could just do method.addClosure(bean, required)...but I'd like to be able to manipulate them really.
Class clazz = ...;
Object bean = ...;
Method method = ...;
if (methodHasTransactionRequired(method)) {
InstanceCallStack stack = method.getInstanceCallStack(bean);
if (!stack.contains(required)) {
stack.add(required);
}
//method.setInstanceCallStack(bean, stack); ?
}
Where InstanceCallStack subclasses Stack and contains closure objects. You can then inspect, add, and remove them. Obviously there are other uses for this. My point is that it accomplishes much of what AOP does (AOP advocates would say it is just a replacement for a method of implementing AOP rather than AOP itself meaning the closure is the encapsulated advice).
At the lower level there would have to be a similar switch as was done in the jit for contended and uncontended locks (syncronized) [pdf] to avoid needless dispatches for the 99.999% of all methods that have a null instance callstack. Indeed initial callstack creation wouldn't be entirely thread safe (meaning any instances about to go into it might get the null instance call stack) and therefore the instance call stack itself might be better off semi-immutable (instead rely on a semi-atomic "set" and any "getInstanceCallStack" is merely a copy thereof). If done correctly this means no or immesurable performance decrease for invocations on methods with a null call stack and stack.size() * methodDispatchPerformance degredation for those with a call stack (excepting what work is actually done in the closures).
So right now as I read the closure spec there is nothing like this and Java still has no runtime meta-model. This would be a first step in my mind towards mutators on Method objects (add a method to an object) and optional dynamic typing (maybe a DynamicObject obj = Class.getDynamicType(object)), but at least it removes the need for 99% of AOP precomplation and postcompilation code magic :-). Sadly except for the first code snip with closures...this all only exists in this blog entry :-).
I'm interested in what people think about this and the Java closures proposal itself so I'm putting it in my semi-spam protected Buni tech tips blog...which was originally going to just be a continuation of my "stupid shell tricks" and "how to" type stuff (which is the ultimate search engine optimization ;-) )...but it has evolved I guess into "stuff I want to say about technology but don't want on the front page" ;-). If you haven't watched the GoogleEDU video on closures you probably should. Might also make the above parse better.
[Trackback URL for this entry]
