Loading Clojure code with java.util.ServiceLoader

This blog post is also available in Swedish here.

Here’s something we learned when working on the game engine for the Citerus coding challenge CrazySnake (in Swedish). Maybe some of our findings about class loading in Clojure and usage of ServiceLoader will be useful for someone out there, so I thought I’d share them.

The competing teams provided their implementations of the Brain interface from our API and packaged the implementation in a jar-file.

The brain was injected into a snake, that moved around autonomously on a game field, eating fruits and avoiding to crash into other snakes or walls. Remember the old game called Snake that used to be on several mobile phones? Something like that, but autonomously and multi-player.

Since we wanted to load the implementations at runtime, we needed dynamic class loading.

The most commonly used approach to dynamic class loading has been simply use of the Class.forName(). However, this only supports loading of classes using their name, and not their type. Since we didn’t want to restrict the names of the implementing classes we simply wanted to load all implementations of the Brain interface that were provided. The ServiceLoader has been around since Java SE 6 and supports dynamic class loading based on a type. The ServiceLoader reads files in the META-INF/services/ directory on the classpath and tries to lazily instantiate classes based on the fully-qualified class-names in these files. The name of the file should correspond to the fully-qualified name of the service (interface) that the classes implement (in our case this was se.citerus.crazysnake.Brain). Look in the examples-projects here to see how it was set up.

We used java.util.ServiceLoader to load each team’s implementation by creating a URLClassLoader that was fed into the ServiceLoader.load(). This looked something like this:

  URL[] urls = {file.toURI().toURL()}; 
  ClassLoader classLoader = new URLClassLoader(urls); 
  ServiceLoader<Brain> services = ServiceLoader.load(Brain.class, classLoader);

The approach we first used (seen above) worked fine for our initial test-brains that were implemented in Java and we were feeling quite happy with the results. However, we also wanted it to be possible to implement the brains in more JVM-supported languages than Java and Clojure was an obvious candidate. The solution worked fine with Groovy and Scala but when trying to implement a Brain in Clojure using :gen-class we stepped into some trouble. Below is the code for a stupid brain implemented in Clojure:

(ns se.citerus.crazysnake.brain.clojure-brain
  (:import
    [se.citerus.crazysnake Movement])
  (:gen-class
    :name se.citerus.crazysnake.brain.ClojureBrain
    :implements [se.citerus.crazysnake.Brain]
    :main false))

;-- Implementation of Brain interface

(defn -init [this participants meta])

(defn -getName [this]
  "ClojureBrain")

(defn -getNextMove [this state]
  Movement/FORWARD)

When we tried to load a Clojure-implementation the ServiceLoader threw an ServiceConfigurationError when accessing its iterator. Some research led us to the knowledge that Clojure uses the current Thread’s context class loader by default. Since the context classloader has no knowledge of our Clojure code (only our created classloader knows this), this didn’t work. This made us understand that we had to set the Thread’s context classloader before trying to call ServiceLoader.load(). The following code made the whole shebang work like a charm:

File brainImplementingJarFile = new File(pathToJar);
URL[] urls = {file.toURI().toURL()};
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
ClassLoader classLoader = new URLClassLoader(urls, contextLoader);
Thread.currentThread().setContextClassLoader(classLoader);
ServiceLoader<Brain> services = ServiceLoader.load(Brain.class, classLoader);

// Reset the context classloader    
Thread.currentThread().setContextClassLoader(contextLoader);

Hope someone will find this useful when working with dynamically loading Clojure classes!