BeansBinding Performance (Issue 37)
Since the release of NetBeans 6.0, BeansBinding (JSR 295) framework popularity has increased. Personally I'm using it now in all my projects. Ease of development and the increase of productivity are worth it. The counterpart is that this project looks dead and hasn't been updated for more than a year now. Even more, recent news about Swing core is not very encouraging.
In my opinion, I believe that Sun's budget is very limited and their recent JavaFx project has consumed all their resources in the past year. Bearing in mind that BeansBinding is now included in NetBeans and that JavaFx is based in Swing, I don't think that neither of the projects is going to be abandoned.
Getting into the subject, BeansBinding has not been updated for a while now and has some bugs that are really annoying. If you use beans binding to bind a form with more than 20 fields you'll notice how the form takes a lot to load.
If you profile your application, you'll quickly notice that the bottleneck is in the statement bindingGroup.bind()
in the initComponents()
section of your code (if you are using Netbeans + Matisse).
Taking a deeper look with the NetBeans profiler tool we get to our point. If you examine carefully the BeanPropery
or the ElPropery
class, the method getBeanInfo(Object object)
is calling Introspector.getBeanInfo(object.getClass(), Introspector.IGNORE_ALL_BEANINFO);
which takes a lot of time.
I Googled to see if I could get any clue about what was happening and if someone else was experiencing the same problem. I found a 2 mail message thread. Not a very popular topic for such a big performance problem.
The thread informs of the created Issue (Issue 37) and suggests a possible solution using USE_ALL_BEANINFO
(which, by the way, is the default flag used if you call the Introspector.getBeanInfo(Class class)
directly).
The slow dialog opening is really pissing me, so I've implemented a solution. Your first approach could be to get the source code and replace the code fragment you need and then recompile the library. There is a great tutorial (Hacking Java Libraries) that teaches you how to do this if you don't know how to do it by yourself. The article is an excerpt of "Covert Java" by Alex Kalinovsky (Sams Publishing, May 2004) (recommended reading).
I don't really like the approach described above. With this technique you can't keep track of the changes you've made and further updates of the library will need to be modified and recompiled again (until the bug is corrected). I prefer to manipulate the bytecode at runtime to reflect the changes I need (BCEL). For this purpose I'm going to use the Javassist library which is very light and straightforward.
In the next code fragment you have the code needed to get the modification working:
1try {
2 ClassPool cp = ClassPool.getDefault();
3 CtClass cc = cp.get("org.jdesktop.beansbinding.ELProperty");
4 CtMethod m = cc.getDeclaredMethod("getBeanInfo");
5 m.setBody("{" +
6 //"assert $1 != null;"
7 "try {" +
8 "return java.beans.Introspector.getBeanInfo($1.getClass());" +
9 "} catch (java.beans.IntrospectionException ie) {" +
10 "throw new org.jdesktop.beansbinding.PropertyResolutionException(\"Exception while introspecting \" + $1.getClass().getName(), ie);" +
11 "} }");
12 Class c = cc.toClass();
13 cc = cp.get("org.jdesktop.beansbinding.BeanProperty");
14 m = cc.getDeclaredMethod("getBeanInfo");
15 m.setBody("{" +
16 //"assert $1 != null;" +
17 "try {" +
18 "return java.beans.Introspector.getBeanInfo($1.getClass());" +
19 "} catch (java.beans.IntrospectionException ie) {" +
20 "throw new org.jdesktop.beansbinding.PropertyResolutionException(\"Exception while introspecting \" + $1.getClass().getName(), ie);" +
21 "} }");
22 c = cc.toClass();
23} catch (NotFoundException ex) {
24 ex.printStackTrace();
25} catch (CannotCompileException ex) {
26 ex.printStackTrace();
27}
The code replaces the method body of getBeanInfo
in ElPropery
and >BeanPropery
with the modified code that suits our needs. In this case, instead of calling Introspector.getBeaninfo(Class class, int flag)
it calls Introspector.getBeanInfo(Class class)
which by default uses the cached information, improving performance. For all of this to work, it's very important to run this code before any other code which uses the BeansBinding library is called. If you can't do that, read the Javassist tutorial to build a loader or use HotSwapper.
I don't know why Shannon Hickey decided to use the Introspector.IGNORE_ALL_BEANINFO
flag in the first place, maybe there's a good reason. I can't really think why we shouldn't use the cached info, there's no reason why a bean should change its structure during runtime (Unless someone is using dynamic bytecode engineering BCEL, of course).
I hope this code helps you. Please if you have any comments, suggestions, or improvements, post a comment. Thank you.
Comments in "BeansBinding Performance (Issue 37)"
Thanks for the note. I hadn't even noticed,the issue appears as unfixed. I used to check the subversion, but since there was no point (everything remains the same) I don't check in a regular basis.
As you say, the issue was fixed 4 weeks ago ELPropery
In either case, this is a good example showing how to use javassist.
Thank you for your patch! I've got dialog with nearly 70 items and it took more than 3 seconds to show it, but after I've applied your patch the time has been reduced to just around a second.
But today after finding and implementing your hack , everything works much much much faster and smoother.
Thank you very much for sharing this with all of us.
I really really owe you a lot. A beer??? No. The whole supermarket of beers!!!!!!!!!!!
Cheers!!!!!!!!