 |
JavaBeans, Binding, and AOP
Posted by javaben on June 01, 2006 at 09:19 AM | Comments (8)
My friend Richard Bair recently told the world that the colloquial definition of JavaBeans as an object with getters and setters is incomplete, and demonstrated how a handy base class can make adding PropertyChangeListener support easier.
I'd like to pick up where his discussion left off. Over the past few years, I've been pondering how to make it easier to develop Swing applications. One area of pain in Swing is binding; that is, copying values from Swing widgets to business objects and vice-versa.
Swing components, as Richard mentioned, are observable via PropertyChangeListeners and other types of listeners, so detecting when widget values are changed and copying those values to objects as a result is pretty easy (though there are a few subtleties to get right). The reverse, however, is not free. In order to detect when values in an object have changed, one needs to use some mechanism like the JavaBean spec to introduce observability into JavaBeans.
And for me, therein lies the rub. Firstly, I hate repeating myself, and writing firePropertyChange("property", old, new) over and over again seems a big pain. Second, the string literal gives me the heeby-jeebies. If I'm going to all the bother of using a strongly-typed language, I hate having the bugs of loose-typing introduced with none of the benefit. Third, what if I'm using some pre-constructed business objects that aren't observable and can't easily be made to be observable?
(A quick tangent: Everyone ought to know that walking stacktraces is one of the most expensive VM operations around. Yet, just for fun, I created a version of firePropertyChange(...) that automatically obtains the property name by walking the stacktrace. Here it is:
protected void firePropertyChange(Object oldValue, Object newValue) {
try {
throw new Exception("stacktrace");
} catch (Exception e) {
StackTraceElement[] st = e.getStackTrace();
String property = ReflectionUtils.getPropertyFromAccessor(st[1].getMethodName());
firePropertyChange(property, oldValue, newValue);
}
}
Never use this code in a tight loop.)
For all these reasons, the binding solution I use takes a hybrid approach. To save myself a lot of explanation, let me just say that when the Swing widget's value is changed, the framework can copy the value to the business object automatically, but the framework does not copy the value back to the Swing widget until the developer specifically requests that the value be copied from the business object back to the Swing widget.
In practice, it looks something like this:
binder.defineBinding(businessObject, swingWidget, "fieldName");
// at some future point, call this next line to update the UI from the object
binder.updateWidgets();
I've used variations on this technique for a while now, and it works very well. When combined with a container-managed form system, it actually works nearly as well as true bi-directional binding as the form lifecycle can introduce automatic calls to binder.updateWidgets() at various flow points (and the automatic behavior can of course be disabled).
But I've been aware for a long time what the real solution to this problem is: Aspect-Oriented Programming (AOP). Using AOP, you can easily add PropertyChangeListener support to any object, even those for which you don't have source code, and generally solve all three of the problems I have with manually adding PropertyChangeListener support.
Unfortunately, I don't use an IDE that has AOP support, so I haven't been able to explore AOP much up to now. But the latest release of my favorite AOP framework, AspectJ 5, enables a new type of AOP syntax based on Java 5 annotations (called @AspectJ).
Using @AspectJ, I can create a simple class that handles all of this property firing for me:
@Aspect
public class JavaBeanObservabilityAspect {
@Around("execution(* *.set*(..))")
public void timeElapsed(ProceedingJoinPoint jp) throws Throwable {
if (jp.getTarget() instanceof JavaBean) { // only fire listener if a JavaBean
JavaBean o = (JavaBean) jp.getTarget();
String property = ReflectionUtils.getPropertyFromAccessor(jp.getSignature().getName());
Object oldValue = ReflectionUtils.invokeAccessor(o, property));
// proceed with the invocation of the method
jp.proceed();
Object newValue = jp.getArgs()[0];
o.firePropertyChange(property, oldValue, newValue);
} else {
// if not a bean, just proceed with the invocation normally
jp.proceed();
}
}
}
The above is a crude beginners example of how to automatically fire the event on all JavaBeans -- a more refined example would show you how to actually add PropertyChangeListener support dynamically to any object. The example I gave is also problematic in that if the setter already fired the event, it would be fired again by the aspect. And, I coded this version here in my blog, so if it doesn't compile, don't sue me. But... you get the idea.
The beauty of @AspectJ is that because it just uses normal Java 5 annotations, you can use your IDE to create and compile the aspects like any other part of your code base. To actually use the aspects at run-time, you must weave the aspect into your code.
Weaving is the process of augmenting Java classes with the additional instructions defined by one or more aspects (such as the one above). You do that with a special compiler that comes with Ant tasks that can be easily introduced into your build process.
But for the impatient, there's this cool new thing in ApsectJ 5 called load-time weaving ("LTW"). With LTW, you pass your JVMTI compliant (Java 1.4+) VM a property that configures an agent that can weave your classes as they are loaded:
-javaagent:aspectj/aspectjweaver.jar
You then create an XML file (META-INF/aop.xml) that specifies with aspects to weave, and into which classes, such as:
<aspectj>
<aspects>
<aspect name="org.galbraiths.beans.JavaBeanObservabilityAspect"/>
</aspects>
<weaver options="-verbose -showWeaveInfo">
<include within="org.galbraiths.beans.JavaBean"/>
</weaver>
</aspectj>
And presto! You've got AOP in your project (along with a handy speed hit whenever the affected classes are loaded for the first time). There are all kinds of fun things to do with AOP, such as introducing ultra-low-intrusion microprofiling, verifying proper Swing threading, and so forth. Blogs for other days.
So -- for the purposes of binding, you may not need observability in your objects, but if you do want it, consider AOP for adding observability without repeating yourself all over your codebase -- and for adding it to code you don't control.
If the pretty good AspectJ project documentation doesn't do it for you, check out AspectJ in Action, written by Ramnivas Laddad -- a friend and collegue whom I greatly respect. His book doesn't cover @AspectJ, but is widely considered the best AspectJ book around.
As with most of my entries, this was cross-posted on my personal blog, Married... with Children
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
genesis also implements UI binding approach by using AOP. Although this docs only mention Thinlet, it already supports Swing (I'm currently updating the docs). It doesn't require PropertyChangeListener at all.
Posted by: mister__m on June 01, 2006 at 12:27 PM
-
Lacking proper language support for properties, annotation based aspects sounds like a really reasonable alternative. Although, truthfully, I've been writing these observable setter methods for a while now and it's not that big a deal. If JAXB and my IDE would auto generate this stuff, that'd be better. One problem I have with aspects is that sometimes setting one property affects several: for example setting the "name" property may affect the "name", "firstName" and "lastName" properties. In such cases, the setName() method is going to have to fire property change events for all 3 properties. Not sure how elegantly this is handled by the Aspects approach.
Posted by: rbair on June 01, 2006 at 08:03 PM
-
Ben,
I was told the concept of a 'Java Bean' came from the Borland Delphi folks working with the AWT/Swing team. Could you ask Hans if this is an urban legend? :-)
Seriously, I remember reading about 8 years ago how a few folks from Borland and Sun hashed out ways to make it easy to put a GUI builder on top of a GUI tool-kit. Ironically, what you presented at Java One during the '8 ways to build quicker Swing apps' and the NetBeans IDE are both just now getting to the usability of Delphi around the late 90's. ;-)
I stopped using Delphi around 1998 and heard they killed it converting it to a C# IDE tool. The Delphi C# IDE was not usable based on old friends that I knew were still coding in Delphi.
That reminds me.... :-D
Are the code examples from Java One up on your website now?
Posted by: cupofjoe on June 01, 2006 at 08:04 PM
-
rbair: Actually, not that hard to solve the compound property problem. Just use an annotation, say, @FiresPropertyChange, and if the method has that annotation, the aspect won't auto-fire the event. I just can't bring myself to write all those fire... invocations manually ;-)
cupofjoe: Riiiiiight... the sample code. :-) Going to post that this weekend. Yeah, I think everyone admits with a depressed sigh that the state of Swing tools and frameworks hasn't held a candle to some of the high productivity GUI environments of yesteryear. But, when compared to some other frameworks, I think it stands out just fine.
Posted by: javaben on June 01, 2006 at 08:24 PM
-
Coool. I'd love to see the aspects approach in practice.
Posted by: rbair on June 01, 2006 at 08:43 PM
-
AspectJ 5 seems really cool, I especially like the load-time weaving. Just switch in another jar of aspects before you run and BAM...
Posted by: tobega on June 02, 2006 at 04:45 AM
-
Ben,
Swing and AWT are TOTALLY awesome. What they have done I had given up on at one point in my career. I used a C++ tool called AppWare in another life.
http://en.wikipedia.org/wiki/AppWare
It had a great UI builder but very limiting API widgets. Swing it a LOT better even without the builder.
Just to prove how great Swing is. When I was at Java One MS had a booth trying to convince me to code rich clients using XForms or something like that. Just like Swing but bound to the 'captive' X drivers for speed and ONLY runs on Windows.
Is it just me or does anyone find it ironic that a Swing application will run on MORE of Microsofts GUI platforms than C#? :-)
Posted by: cupofjoe on June 02, 2006 at 08:47 AM
-
The load-time aop weaving is fantastic. I have a lot of eclipse projects that I have open all the time. I also have a builder that I used to weave AOP functionality into the projects. The weaving was very time consuming. Once the load-time weaving was introduced, I could take the compile-time AOP weaving out. Cleaning all the projects now takes only seconds.
Posted by: calfonso on June 15, 2006 at 08:57 AM
|