Tuesday, July 17, 2012

Tips for interactively debugging CDI applications

I've grown to really like Contexts and Dependency Injection (JSR299, CDI), a part of the Java EE 6 suite. It's relatively simple and clean, it's extensible, and it allows for a really nice loosely-coupled programming model. It gives you the freedom to use event-driven or direct call operation and can take care of most of your lifetime/lifecycle issues for you.

Of course, this is my blog, so you know there's a "but". Sure enough there is, albeit a pretty minor one: It's a mess for interactive debugging, because stepping through a CDI invocation takes you through layers of weld proxies, scope lookups, and all sorts of other crud you usually don't want to see. LOTS of it. CDI isn't unique in this respect, as anyone who's stepped through EJB calls will know, but it's perhaps worse than some due to extensive use of proxies, interceptors, etc.

There's a solution in Eclipse. It could be simpler and it could be more complete, but it's a heck of a lot better than nothing.

If you're using an interactive debugger with Eclipse it has a feature that will help: step filters, accessed via Window -> Preferences, Java -> Debug -> Step filtering.

Check "Use step filters" if it's unchecked. As noted in the above documentation, you can toggle this easily with the "step filters" button on the debug toolbar.

Enable "filter static initializers", "filter constructors", "filter synthetic methods", "filter simple getters" and "filter simple setters". I wish "Filter constructors" could be closer to "filter out constructors of proxies and temporaries" but alas, no. Just remember it's on.

Now add the following list of filters for JBoss AS 7.1.1.Final. The list will be different for other app servers and may require amendment even for small version changes:

com.arjuna.*
org.jboss.as.*
org.jboss.logging.*
org.jboss.logmanager.*
org.jboss.invocation.*
org.jboss.msc.*
org.jboss.weld.*
org.slf4j.*
sun.misc.*
sun.reflect.*
java.lang.*
java.util.*


You should now be able to "Step into" a CDI invocation and go straight to the endpoint that's been invoked. You'll see that all the intermediate steps are shown in the tree, they just aren't paused for.

If you're not interested in any of the guts and don't use any JBoss code you might want to step into in your projects, you may well wish to exclude all of org.jboss.*. It can also be handy to exclude javax.*, sun.* and sunw.* etc.

The same approach can be used to hide the guts of EJB invocations, the deep call paths that lead to your entry points, and various other noisy calls you aren't interested in. For example, I've added:

org.apache.coyote.*
org.apache.catalina.*
com.vaadin.*

... and now when I "step return" or step to the end of one of my entry point methods execution resumes automatically instead of percolating back up the deep call chain in the server.

I arrived at this list by repeating the following process:
  • Step into breakpoint
  • Note package of class that execution stopped in
  • If it isn't one I want to see, add the coarsest reasonable package wildcard to the step filter.
  • Goto start
... until I could step straight through the intermediate crap. It's still there cluttering up my call chain in Eclipse, making me miss Netbeans' feature where it collapses intermediate plumbing and proxy calls, but at least I'm not constantly pausing in it anymore.

Just remember that these filters are there, because they might confuse you later, especially the filter on java.lang.* and java.util.*.

Eclipse's debugger has some other gems that you owe it to yourself to look into, too. Conditional breakpoints, of course. "Logical structures" is amazing, as is the "Detail formatters" feature. It's worth learning about.

If you're adventurous you can even check out Mylyn which offers task-focused debugging including stack filtering. It's explained in nearly human terms here. Mylyn is sometimes a bit overzealous, though, and makes it easy to filter out the clue you needed to figure out what was wrong..

No comments:

Post a Comment