|
[]
Undo in AspectJ
[
tirsen
]
On an internal ThoughtWorks conference I presented a couple of cool examples of what you can do with AspectJ (avoiding the rightfully impopular and boing logging example, which was appreciated by the audience). Most of them was blatantly stolen from Ramnivas Laddad's superb book AspectJ in Action (hope you don't mind Ramnivas ;-) ). Read it if you haven't yet, I cannot recommend it enough. I rewrote them using TDD on the train back and forth to my current project outside of London. One of the more popular ones (and one of the few I did not steal from Ramnivas) was the undo framework. Anyone that has written a large GUI application knows that undo is a major pain in the ass, each command has to written twice, how to do it and how to undo it. Well, with AspectJ things like this becomes quite easy and can be written completely modularized in a generic "non-intrusive" fashion. Here's a test-case that demonstrates the usage of the framework:
UndoChain undoChain = new UndoChain();
final Point point = new Point(100, 100);
undoChain.execute(new Runnable() {
public void run() {
point.x = 150;
}
});
assertEquals(150, point.x);
undoChain.undo();
assertEquals(100, point.x);
undoChain.redo();
assertEquals(150, point.x);
A Point is created and the x-coordinate is modified in a command. undoChain.undo() rolls back all modifications done by the command and undoChain.redo() reapplies them again. Ok, so here's the gist of the solution, an aspect that records all field modifications their previous value and their new value.
aspect RecordFieldModifications
{
/**
* Picks out the execution of a command.
*/
pointcut inCommand(Command cmd) :
this(cmd) &&
execution(* Command.execute());
/**
* Picks out any modification of a field.
*/
pointcut fieldModification(Object o, Object newValue) :
args(newValue) && this(o) &&
set(* *.*);
/**
* Picks out any modification of a field caused by the execution of
* a Command, the old value can unfortunately not be picked out by
* the pointcut. :-(
*/
pointcut fieldModificationFromCommand(Command cmd, Object o, Object newValue) :
!within(UndoChain) &&
cflow(inCommand(cmd)) &&
fieldModification(o, newValue);
/**
* Advice to any field modification caused by the execution of a field. Stores
* the new value and the old value as a Modification on the Command.
*/
before(Command cmd, Object o, Object newValue) :
fieldModificationFromCommand(cmd, o, newValue)
{
// retrieve extra context information via reflection
Field field = getField(thisJoinPoint.getSignature());
Object oldValue = field.get(o);
// record modification in currently executing command
cmd.addModification(o, field, oldValue, newValue);
}
/**
* Gets the field using reflection.
*/
private Field getField(Signature signature) {
Field field = signature.getDeclaringType().getDeclaredField(signature.getName());
field.setAccessible(true);
return field;
}
}
The rest of the solution is basically just plumbing, to undo a command all modifications are undoed in sequence. Undo of a single modification is basically just about restoring the old value of the field using reflection: field.set(o, oldValue); The whole solution clocks in at 156 lines of code, which includes imports, package statements, and quite lengthy comments.
Neat, huh!?
RE: Jon Tirsen's "Undo in AspectJ", and Compensating Transactions "This Undo framework could be perfect for the Transaction Aspects that we have in aTrack. I bet there would be a place to create a Compensating Transaction system which would allow you to use AspectJ to cleanly setup your compensating transactions." --Dion Almaer, April 26, 2004 05:39 PM
Great stuff! You are welcome to use examples from "AspectJ in Action" in your presentations :-) -Ramnivas --Ramnivas Laddad, April 27, 2004 10:21 PM
This is a very spiffy solution to the undo problem. How would you handle undo for this: List somelist = myExcitingBusinessObject.getInfo(); I have a similar need to track changes to objects (though I hadn't thought of doing undo this way!). My thinking on this so far has been to wrap returned Collection objects to keep track of changes to the collection. Have you run into this need? Kevin --Kevin Dangoor, May 10, 2004 01:44 AM
Good spotting, Kevin! I realized this flaw to my solution and I was just waiting for someone else to spot it too. :-) My current idea on how to do this is to simply use the memento pattern to make a backup of each object as it is touched (ie method invoked on it etc) during the execution of a command. This would certainly make backups of more objects than it would actually need, but I'm not sure the overhead is going to be significant or not. In some cases it could actually give better performance because: 1) it would only make one copy of the object as it is touched the first time, 2) because each field access is not adviced, 3) it avoids a lot of reflection. It's not as magical as the above one too, which is good, I'm not that fond of magical stuff. Crazy Bob insists you need to use proxies (a la Dynaop) to do this. But I'm not sure that's an optimal solution, and it also gives quite a significant runtime overhead. --Jon Tirsen, May 10, 2004 11:11 AM
Nice! like it a lot. Also, another concern I can batter the tedious AO sceptics with. btw. pedant point: when something has had undo applied it is not undoed but undone dude !-) --Jed Wesley-Smith, May 12, 2004 06:23 AM
Hi, I've got a question. You write "Picks out any modification of a field caused by the execution of a Command, the old value can unfortunately not be picked out by the pointcut. :-(" - Could that not actually be done, though, by an around advice? I am not entirely sure, but if you have a piece of advice that shadown the "set" event, then the exposed value should actually be the old one before you proceed, shouldn't it? Eric --Eric Bodden, October 7, 2004 07:27 AM
Hi. I just found a way to generally get "old field values" without reflection. I blogged about it here: --Eric Bodden, May 14, 2005 11:11 AM
hkmtteuy [URL=http://zanujehc.com]sscxwgio[/URL] gpgqvtlj http://soplpznc.com jmlfpmzf tnstgwia --kbogrjov, October 23, 2007 04:11 PM
[URL=http://ztdplazz.com]rjhbgixa[/URL] kwukztip http://ipvfcwhy.com fwqvndxg prmhfvqo wzasyqga --xrxtddjt, October 23, 2007 04:11 PM
mrybhiqt http://ibqbxazr.com nqnxspqz afnjrodl [URL=http://anmcihgd.com]sxduhxzi[/URL] javzhjjz --iwqyotlz, October 23, 2007 04:12 PM
tibffjwb http://pknypecs.com seumgsek rkisihcs [URL=http://nsqffigk.com]dckqoayy[/URL] tjzkjlfo --wyxpyvpc, October 23, 2007 04:13 PM
Post a comment
|