I rarely use the :gen-class facility in Clojure, but when I do, I usually trip over one issue or another. After finally spending some time to read the documentation and peruse source code, I've got a better understanding of how it all fits together.

Write that down, write that down!

When Clojure code is evaluated, the compiler generates JVM bytecode and loads it into memory. For various reasons, such as improved startup time, it can be advantageous to compile namespaces into actual classfiles. These compiled namespaces need to be :import'ed instead of :require'ed.

A namespace is normally compiled into a class ending in __init. For the simplest namespace:

(ns clj-genclass.core)

We can compile with Leiningen:

lein compile clj-genclass.core

And see the class core__init.class under target/classes/clj_genclass.

Lookin' fresh

The resulting class isn't very convenient to work with, however. That's where :gen-class come in: it provides a large number of options1 that make the generated class more like the Java library classes we interop with.

To automatically compile classes before starting a Lein REPL, the namespace needs to be added under :aot in project.clj. Consider the following:

(ns clj-genclass.MyClass
  (:gen-class
   :constructors {[String] []}
   :init init
   :state state
   :methods [[print [] void]]))

(defn -init
  [txt]
  [[] txt])

(defn -print
  [this]
  (println (.state this)))

Running lein repl, we see that the class is compiled:

$ lein repl
Compiling clj-genclass.MyClass
nREPL server started on port 57734 on host 127.0.0.1 - nrepl://127.0.0.1:57734

And can be immediately imported and used:

clj-genclass.core=> (import 'clj_genclass.MyClass)
clj_genclass.MyClass

clj-genclass.core=> (.print (MyClass. "My own class"))
My own class

The function is dead, long live the function

If we update MyClass's -print method, e.g.

(defn -print
  [this]
  (println "foo" (.state this)))

We can see the changes take place either by compiling the class in the REPL:

clj-genclass.core=> (compile 'clj-genclass.MyClass)
clj-genclass.MyClass

clj-genclass.core> (import 'clj_genclass.MyClass)
clj_genclass.MyClass

clj-genclass.core> (.print (MyClass. "My own class"))
foo My own class

Or just evaluating it in CIDER. lein compile does not work because it only writes to classfiles and doesn't load anything into memory.

The old switcheroo

Classes in Java are loaded by instances of ClassLoader2. Once an instance of ClassLoader has loaded a class, it must always return what it already loaded. This means that it cannot reload a class whose definition has changed.

To get around this, Clojure takes advantage of the fact that a class is distinguished not just by its name but also by its classloader. Every time a form is (re)evaluated, a new instance of Clojure's own DynamicClassLoader is used. We can see this in action:

clj-genclass.core=> (defn foo [] "foo")
#'clj-genclass.core/foo

clj-genclass.core=> (.getClassLoader (.getClass foo))
#object[clojure.lang.DynamicClassLoader 0x41f045a1 "clojure.lang.DynamicClassLoader@41f045a1"]

clj-genclass.core=> (defn foo [] "bar")
#'clj-genclass.core/foo

clj-genclass.core=> (.getClassLoader (.getClass foo))
#object[clojure.lang.DynamicClassLoader 0x32035f8f "clojure.lang.DynamicClassLoader@32035f8f"]

When foo was redefined, the new version was loaded by a different classloader. All instances of DynamicClassLoader share a cache so they can all find previously loaded classes. Classes that are no longer used (such as those that have been redefined) are eventually removed.

Footnotes:

1

:gen-class by itself does not compile a class–it only specifies how a class should be compiled.

2

Except for the classes of the Java Runtime itself, which are loaded by a boostrap classloader written written in native code.