It seems every time I come back to my Spring/Hibernate/AspectJ project after a break, I have a ritual of going through at least one long and frustrating debugging session in the first few days. Most times it seems to be yet another case of the attack of the hidden globals. I know all about these lurkers, and they’re even well documented, but like most hidden global issues, they manifest differently every time. So I’m documenting them here in the off-chance I’ll think to look at my blog next time it happens.
Suspect #1 – the Spring-configured aspect
AspectJ aspects are global to the classloader. Unless your app is doing funky things with classloaders, that’s about as global as you ever need to worry about. In particular, they are much more strongly “singleton” than normal Spring bean singletons, which are singletons only within a particular Spring ApplicationContext, and it’s this difference that gets me into trouble. It’s quite reasonable in Spring to load multiple contexts, even from the one configuration file. In this scenario each context has its own nicely encapsulated set of singletons except for the aspects. Basically, aspects are impossible to encapsulate, and all contexts will share the one copy of each aspect.
All this is fine until you start injecting configuration state into the aspects via Spring. At that point you have a serious breach of encapsulation, with the different context configurations racing to overwrite the state of the shared aspects.
Lest you think you have to be doing wacky things to get into this state, see http://forum.springsource.org/showthread.php?48088-aspectOf-breaks-context-cache-isolation-in-unit-tests. The Spring @Configurable annotation can easily be set up to use AspectJ, and the Spring test utilities by default will load multiple contexts. So with about three lines of standard Spring usage you’re suddenly not in Kansas anymore.
The other cautionary tale here is how easy it is to leave Kansas without intending to. In my most recent bout, the change that triggered the attack of the globals was simply to edit a single test class to add an extra Spring config file. Kaboom! Obviously something wrong with that config file, right? Wrong. The fact that this test class had a slightly different Spring config meant it got its own ApplicationContext (see http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/testing.html#testcontext-ctx-management-caching), which then proceeded to stomp all over the aspect configuration for all the other tests.
Now, I must emphasise that all of this is well understood and well documented, and (with the possible exception of https://jira.springsource.org/browse/SPR-6121) can’t in any sense be said to be a bug. The take home message is, in an AspectJ project spring contexts are NOT encapsulated. Hardly earth-shattering news, as the whole point of AspectJ is to break encapsulation in interesting ways, but easy to forget when the aspects are mingling in your config files with normal meek and mild Spring beans.
Suspect #2 – ThreadLocals
Again these bad boys are way more global than any normal language construct has any right to be. They serve a legitimate need, but like aspects they are an escape hatch from normal rules of encapsulation and as such can consume inordinate amounts of debugging time.
In my situation ThreadLocals come into play via Spring’s transaction management, which stashes transaction state in a ThreadLocal. Thinking about it, what else could Spring do? We want declarative transaction management without explicit dependencies on Spring. Obviously if our application isn’t going to talk to Spring, Spring has to keep its state somewhere global.
The downside is not so much that any use of ThreadLocal in a thread-pooling environment absolutely requires cast-iron clean up of any leftover state before we release the thread – that’s just good practice – but that if you slip up somewhere, the resulting bugs will be devastingly hard to find. In general, use of ThreadLocals is buried way deep in the framework. You shouldn’t need to know or care about it, but if you have this kind of bug, you will get nowhere until you find out that there is indeed a ThreadLocal in play.
In my case I discovered that my app server’s post-request cleanup could under certain circumstances fail to fire. To discover that, I had to spend several days in the debugger single-stepping through Spring and Hibernate framework code. A fascinating experience, but not what I thought I was signing up for when I typed in the ten lines of config for Spring’s transaction manager.
In summary: these frameworks are doing some serious magic to provide some really impressive levels of transparency and convenience. 99.9% of the time they just work and you never need to know or care what’s happening under the hood. However, awareness is half the battle. Even if you’re a super-conservative coder who eschews “advanced” techniques in your own code as not worth the cost in debug time, by using these frameworks you are in fact deploying singleton aspects, multiple contexts, and ThreadLocals. So, if the day comes (and may it never arrive for you!) when your application is possessed by demons and you have to open up the black box, these suspects are a good place to start looking.