Groovy Grape – Adding Dependencies to Root ClassLoader

When using grape in groovy to download dependencies and add to classpath, they will by default be added to the GroovyClassLoader when the script is run. This can be a problem, if these dependencies needs to be uses from classes higher in the classloader hierarchy. One common example would be when loading JDBC drivers, because these are loaded using Class.forName. So, this code will fail with ClassNotFoundException:

@Grapes([
   @Grab(group = 'oracle', module = 'ojdbc15', version = '11.2.0.1.0'),
])
class SqlTool {
  private sql;

  public SqlTool(url, driver, username, password) {
    this.sql = Sql.newInstance(url, username, password, driver)
    // ... do sql stuff
  }
}

new SqlTool("jdbc:oracle:thin:@dbhost:1521:dbsid", "scott", "tiger", "oracle.jdbc.driver.OracleDriver")

Because the oracle driver will be in the GroovyClassLoader, which is down the hierarchy relative to java.lang.Class, which is up in the root classloader, the loading will fail.

The @Grab annotation seems not to support the definition of which classloader to add the dependencies to, but luckily we have programmatic access to grape too. What we need to do is something like this:

import groovy.sql.Sql

def classLoader = this.getClass().getClassLoader();
while (!classLoader.getClass().getName().equals("org.codehaus.groovy.tools.RootLoader")) {
  classLoader = classLoader.getParent()
}

// force grape to use the root classloader - to ensure that Class.forName works for dependencies
groovy.grape.Grape.grab(group:'oracle', module:'ojdbc15', version:'[11.2.0.1.0,)', classLoader: classLoader)

class SqlTool {
  private sql;

  public SqlTool(url, driver, username, password) {
    this.sql = Sql.newInstance(url, username, password, driver)
    // ... do sql stuff
  }
}

new SqlTool("jdbc:oracle:thin:@dbhost:1521:dbsid", "oracle.jdbc.driver.OracleDriver", "scott", "tiger")

April 19, 2010 В· polesen В· 4 Comments
Tags: , , ,  В· Posted in: Programming

4 Responses

  1. Mark O'Connor - April 20, 2010

    Groovy 1.7 has enhanced the Grab annotation. You can not define the class loader.

    For example:

    @Grapes([
    @Grab(group='org.apache.axis2', module='axis2-kernel', version='1.5.1'),
    @Grab(group='org.apache.axis2', module='axis2-adb', version='1.5.1'),
    @Grab(group='org.apache.axis2', module='axis2-transport-local', version='1.5.1'),
    @Grab(group='org.apache.axis2', module='axis2-transport-http', version='1.5.1'),
    @Grab(group='xerces', module='xercesImpl', version='2.6.2'),
    @GrabConfig(systemClassLoader=true)
    ])

  2. polesen - April 20, 2010

    Thanks. Didn’t know that.

  3. JT - August 4, 2010

    Thanks!!! I was googling all afternoon on this issue. Got me over the hump in getting my first Groovy shell script to read from a postgres DB.

    Cheers!

  4. Getting Goovy with DB access from shell scripts | JT's Blog - August 4, 2010

    [...] There is one gotcha with using Grape and shell scripts. Grape adds the dependencies to to the Groovy class loader, not Java. A small, yet important distinction. This blog post saved me. ad [...]