Wednesday, September 15, 2010

Using a custom WebAppClassLoader in Jetty

I recently ran into a situation where I wanted to log details about what Jetty's class loader was doing for one of our web apps. There is a hook in WebAppContext to provide your own WebAppClassLoader implementation, which was just what I needed, so I proceeded to write LoggingWebAppClassLoader (source below). The trouble was: where do I actually get a chance to insert my custom implementation?

It's easy enough to do this if you're embedding Jetty in your app:

// Scala code 
val context = new WebAppContext()
val lwacl = new LoggingWebAppClassLoader(context)
context.setClassLoader(lwacl)
...

Unfortunately we're not embedding, but just deploying a .war to an existing Jetty server. I messed around with this at length, and eventually asked on StackOverflow. The answer there got me on the right path, though I had a few other issues along the way.

Here's how I got it working:

  • Changed my deployment technique to Jetty's context deployer ($JETTY_HOME/contexts) instead of just copying .war files into $JETTY_HOME/webapps.
  • Wrote myapp.xml (see below) to define the context. It's in here that you can configure Jetty to use the custom WebAppClassLoader.
  • Copied the jar file containing my LoggingWebAppClassLoader class into $JETTY_HOME/lib so the class is available to the context deployer.

Of course it seems pretty straight-forward now that I've figured it out :) The biggest issue was that I didn't know much about deploying via Jetty contexts. They seem to have a lot of advantages over the vanilla war deployer in that you can easily tweak any Jetty internals at deploy time. Downside is "programming" in XML...

myapp.xml (Jetty context):
<Configure id="mycontext" class="org.mortbay.jetty.webapp.WebAppContext">
  <Set name="contextPath">/</Set>
  <Set name="war">/foo/myapp.war</Set>
  <Set name="classLoader">
      <New class="fully.qualified.name.LoggingWebAppClassLoader">
          <Arg><Ref id="mycontext"/></Arg>
      </New>
  </Set>
</Configure>
LoggingWebAppClassLoader.java:
import java.io.IOException;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.jetty.webapp.WebAppClassLoader;

public class LoggingWebAppClassLoader extends WebAppClassLoader {
  public LoggingWebAppClassLoader(ClassLoader parent, WebAppContext context) throws IOException {
      super(parent, context);
  }
  public LoggingWebAppClassLoader(WebAppContext context) throws IOException {
      super(context);
  }

  private void log(String s) {
      System.out.println(s);
  }

  @Override
  public void addClassPath(String classPath) throws IOException {
      log(String.format("addClassPath: %s", classPath));
      super.addClassPath(classPath);
  }

  @Override
  public Class loadClass(String name) throws ClassNotFoundException {
      log(String.format("loadClass: %s", name));
      return super.loadClass(name);
  }
}

No comments: