brianm/jdbi — pull request #40 by Xorlev (2012-10-03 22:57:22+00:00)
Title: JDBI & Grails 2.1.0 - Classloader Issues

My company has been pretty interested in JDBI for use with an application we're planning on splitting out of Grails (2.1.0) eventually. When we started using it, development went rather well (tests were all passing, though it was our folly to trust the IntelliJ test runner) until came time to run the application. On our machines, we'd see InvocationTargetExceptions caused by NoClassDefFoundError exceptions. I managed to narrow it down to classloader issues.

Some output we'd see (some details cut):

Message: java.lang.reflect.InvocationTargetException-->null
Line | Method
->> 237 | create in org.skife.jdbi.cglib.core.AbstractClassGenerator
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 377 | createHelper in org.skife.jdbi.cglib.proxy.Enhancer
| 285 | create . . . . . . in ''
| 70 | buildSqlObject in org.skife.jdbi.v2.sqlobject.SqlObject
| 60 | open . . . . . . . in org.skife.jdbi.v2.sqlobject.SqlObjectBuilder
| 326 | open in org.skife.jdbi.v2.DBI

Caused by InvocationTargetException: null
->> 384 | defineClass in org.skife.jdbi.cglib.core.ReflectUtils
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 219 | create in org.skife.jdbi.cglib.core.AbstractClassGenerator
| 377 | createHelper . . . in org.skife.jdbi.cglib.proxy.Enhancer
| 285 | create in ''
| 70 | buildSqlObject . . in org.skife.jdbi.v2.sqlobject.SqlObject
| 60 | open in org.skife.jdbi.v2.sqlobject.SqlObjectBuilder
| 326 | open . . . . . . . in org.skife.jdbi.v2.DBI

Caused by NoClassDefFoundError: com/fullcontact/api/cab/mysql/contactdata/ContactData
->> 631 | defineClassCond in java.lang.ClassLoader
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 615 | defineClass in ''
| 384 | defineClass . . . . in org.skife.jdbi.cglib.core.ReflectUtils
| 219 | create in org.skife.jdbi.cglib.core.AbstractClassGenerator
| 377 | createHelper . . . in org.skife.jdbi.cglib.proxy.Enhancer
| 285 | create in ''
| 70 | buildSqlObject . . in org.skife.jdbi.v2.sqlobject.SqlObject
| 60 | open in org.skife.jdbi.v2.sqlobject.SqlObjectBuilder
| 326 | open . . . . . . . in org.skife.jdbi.v2.DBI

All of the class files existed, and we even rewrote them as Java interfaces to see if that was the issue. No dice.

I narrowed this down to running Grails using the internal (embedded) Tomcat or test runner. In a production environment (e.g. deployed to a Tomcat server), all of our JDBI code worked flawlessly.

At that point, I suspected a classloader issue. Grails is pretty *special* in that not only does it subclass the traditional Groovy classloader to workaround some exceptional cases, but I believe it also wraps everything in a smart classloader to feed its code reload facility in development mode. All of this is built on top of the Spring Framework and ostensibly allows use of any nice Spring features, AspectJ weaving, AOP proxies, etc.. The development mode of Grails is potentially one of the most classloader hostile places I've worked in.

Today I took a dive into the JDBI source code and managed to wrangle a fix which at least allowed us to run tests and develop locally using JDBI and Grails. That being said, it's basically an ugly hack where I used `Class.getClassLoader()` to set the classloader on the Enhancer.

I don't know CGlib enough to understand the implications of this solution and would like some feedback.

My original revision of this patch passed the classloader up from DBI all the way through to SqlObject.buildSqlObject. This patch also works, but it also may have side-effects.