 |
Gooey Bean Aspect
Posted by evanx on January 15, 2007 at 06:36 AM | Comments (4)
Gooey Bean Aspect
We use CGLIB
to enhance a half-baked Java Bean with no firePropertyChange()
invocations in its setters, into a bean that does fire PropertyChangeEvent's
from its setters.
Click here to read "Gooey Bean Aspect, a tale of inner discovery"
A part of "Gooey Beans, a trilogy in 42 parts"
Code Snippet
Our QBeanInterceptor registers the PropertyDescriptor's setters methods
into a setterMap.
public class QBeanInterceptor extends QInterceptor {
BeanInfo beanInfo;
Map<Method, PropertyDescriptor> setterMap = new HashMap();
BeanPropertySupport beanAnnotation;
boolean fireByDefault;
...
public Object invoke(Object target, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
Object oldValue = null;
Object newValue = null;
PropertyDescriptor propertyDescriptor = null;
if (args.length == 1 && fireAtWill(method)) {
newValue = args[0];
propertyDescriptor = setterMap.get(method);
if (propertyDescriptor != null) {
oldValue = getOldValue(target, propertyDescriptor);
}
}
Object result = super.invoke(target, method, args, proxy);
if (propertyDescriptor != null) {
QBean bean = (QBean) target;
bean.getPropertyChangeSupport().firePropertyChange(
propertyDescriptor.getName(), oldValue, newValue);
}
return result;
}
...
}
where if the method is a key to an associated PropertyDescriptor
in setterMap, then we invoke firePropertyChange()
to fire a PropertyChangeEvent.
Demo
Here is a trivial demo that is hardly worth downloading. And it's quite large,
because it depends on a CGLIB jar.
(BeanAspectDemo, 250k/850k, unsandboxed, Java6)
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
The problem with using method aspects/proxies to generate property events is when setting one property results in changing another property. For example:
public void setEnabled(boolean enabled) {
this.enabled = enabled;
if (!enabled) setValue(0);
}
The 'value' property event would be fired before the 'enabled' property, thus messing with assumptions people could have made. To achieve the (in my opinion) correct behaviour, we have to write all the tedious code:
public void setEnabled(boolean enabled) {
boolean old = this.enabled;
this.enabled = enabled;
_support.firePropertyChangeEvent(this, old, enabled);
if (!enabled) setValue(0);
}
I'm no AspectJ expert, but I think it'd be possible to intercept the attribute asignment directly instead of the method call. This would solve this problem, but I don't know if it could cause some others.
Whatever...
Posted by: ronaldtm on January 17, 2007 at 03:23 AM
-
ronaldtm, thx for your interesting comment. I guess in such cases, you would not annotate the setEnabled() method ie. not request automatic firePropertyChange().
I'm playing Javassist at the moment (where you really enhance/modify the class before it is loaded, rather than intercepting methods), and there you can intercept read/write access to a field. But a problem might be that not all properties have attributes behind them?
I think that application of annotations/aspects should be limited to the simplest of common boilerplate, outside of which we revert to java and remove the annotation for that method.
Actually my goal with this article is so that in future articles i can annotate a POJO with @JavaBean, refer to this article, and leave it at that ;)
Incidently, i consider "AOP" potentially handy for EDT switching, eg. applying an invokeAndWait() aspect to a method we invoke from a background worker to update the GUI.
In my current usage, "AOP" is typically just a trivial application of dynamic proxies which at best might inspect for annotations. So i'm not using an AOP framework with pointcut definitions, filters, etcetera.
Today i've been quite impressed with Javassist, and i expect to be building support for a few basic aspects using this requested by annotations eg. @InvokeInEdt, @FirePropertyChange, @Logger, @PublishException.
With Javassist, you can launch the main class using their ClassLoader, and you can register a Translator, to enhance/modify classes that have been requested from the ClassLoader, before they are loaded. You can modify the class using java code in strings, which Javassist compiles into bytecode using its builtin compiler. The jar size is manageable too, ie. 160k for jar.pack.gz. Also you can introduce an Ant task to modify/enhance the classes at compile time, but i haven't looked into that yet because i couldn't quickly find any useful tutorials on that.
Posted by: evanx on January 17, 2007 at 06:30 AM
-
evanx, we have used your proposed approach to our project. I tend to agree with ronaldtm because of the following situation:
public void addChild(Child child) {
boolean b = children.add(child);
if (b)
fire the event here
else
don't fire the event cause adding failed
}
Posted by: montechristos on February 05, 2007 at 12:57 AM
-
montechristos, thanks for the feedback :)
Posted by: evanx on February 05, 2007 at 01:03 AM
|