Dynamically redefining classpath in Clojure REPL

In my previous blog entry Clojure for module development in Google Guice? I presented a way to start Guice from within Clojure REPL. It was fun and turned out easy (yet possibly irrelevant for most people).

It however paved the way for creating the new self-learning environment of mine. The idea is to learn Guice interactively and Clojure REPL seems a viable tool to help me out. I don’t have to use an IDE or a build tool – just me, Clojure REPL, Guice and javadoc. It reminds me the very past days when IDEs weren’t as sophisticated as they are now and booting up one was the last resort when in trouble. It changed, but neither did I and REPL resembles a command line I usually work on that gives me a lot of control and now fun too. Learning Guice and Clojure couldn’t be fancier.

And I’ve faced a question about how to dynamically (re)define the CLASSPATH while in Clojure REPL. I’ve lately been reading up the EJB 3.1 specification and I stumbled upon the java.class.path system property which is used to search for available embedded EJB containers. It made me wonder if I could redefine the property in REPL, so I don’t have to define CLASSPATH up front. It took me a mere half an hour to find out how easy it really was. I’m really getting into Clojure REPL and Guice.

It wasn’t that easy when I first approached the question. I thought it’d be enough to just change the property and be done with it. Wrong.

$ clj -13
CLOJURE_DIR:  /Users/jacek/apps/clojure
CLOJURE_CONTRIB_JAR:  /Users/jacek/apps/clojure-contrib-1.3.0-alpha4.jar
Clojure 1.3.0-alpha4
user=> (System/getProperty "java.class.path")
user=> (import  '(com.google.inject Guice AbstractModule))
ClassNotFoundException com.google.inject.Guice  java.net.URLClassLoader$1.run (URLClassLoader.java:202)
user=> (System/setProperty "java.class.path" ".:/Users/jacek/apps/clojure/clojure.jar:/Users/jacek/apps/clojure-contrib-1.3.0-alpha4.jar:/Users/jacek/apps/guice/guice-3.0-rc1.jar")
user=> (import  '(com.google.inject Guice AbstractModule))
ClassNotFoundException com.google.inject.Guice  java.net.URLClassLoader$1.run (URLClassLoader.java:202)
user=> (.printStackTrace *e)
java.lang.ClassNotFoundException: com.google.inject.Guice
	at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
	at clojure.lang.DynamicClassLoader.findClass(DynamicClassLoader.java:60)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:169)
	at user$eval8.invoke(NO_SOURCE_FILE:4)
	at clojure.lang.Compiler.eval(Compiler.java:6201)
	at clojure.lang.Compiler.eval(Compiler.java:6191)
	at clojure.lang.Compiler.eval(Compiler.java:6168)
	at clojure.core$eval.invoke(core.clj:2680)
	at clojure.main$repl$read_eval_print__5619.invoke(main.clj:179)
	at clojure.main$repl$fn__5624.invoke(main.clj:200)
	at clojure.main$repl.doInvoke(main.clj:200)
	at clojure.lang.RestFn.invoke(RestFn.java:422)
	at clojure.main$repl_opt.invoke(main.clj:266)
	at clojure.main$main.doInvoke(main.clj:361)
	at clojure.lang.RestFn.invoke(RestFn.java:437)
	at clojure.lang.Var.invoke(Var.java:409)
	at clojure.lang.AFn.applyToHelper(AFn.java:169)
	at clojure.lang.Var.applyTo(Var.java:518)
	at clojure.main.main(main.java:37)

That’s where I was stuck. I didn’t really know what to do next. I found the help when I’d seen the line with the clojure.lang.DynamicClassLoader.findClass() method’s call and the solution leapt out at me – to somehow tell the classloader to load the required class definitions from jars given in Clojure REPL. After all, that’s how the dynamic languages work on JVM, I believe.

It turned out that the clojure.lang.DynamicClassLoader class defines the public void addURL(URL url) method. That was it!

user=> (def cl (-> (Thread/currentThread) (.getContextClassLoader)))
user=> cl
#<DynamicClassLoader clojure.lang.DynamicClassLoader@19c5466b>
user=> (-> cl (.addURL (java.net.URL. "file:///Users/jacek/apps/guice/guice-3.0-rc1.jar")))
user=> (import  '(com.google.inject Guice AbstractModule))

Bingo! I don’t think I could achieve similar dynamicity with Java itself without sophisticated build management tools, an IDE, OSGi or…place your favorite solution. Being able to add new jars at runtime made me think everything’s possible now with Clojure REPL, but that’s where another question comes up – is it really the way to do so?

Be Sociable, Share!
This entry was posted in Frameworks, Languages.

6 Responses to Dynamically redefining classpath in Clojure REPL

  1. Meikel says:

    This is exactly what add-classpath does.

  2. Jacek Laskowski says:

    Yay! That’s useful. Many thanks.

  3. Raynes says:

    add-classpath is deprecated because this is something you shouldn’t really be doing. Of course, it’s useful in the REPL.

  4. bulat says:

    Oops! I’m sorry. Tried to put a link to fresh lib, but messed it up.

  5. Terje Dahl says:

    After a long weekend of struggling with trying to make a Clojure Jar-runner, and finally giving up and writing some good-old Java, your revelation of using DynamicClassLoader, in combination with some code form Pomagranade and own code, has given me a pure Clojure solution:
    It now loads the jar, discovers the main-class form the maniftest, and runs it!
    Now my uber-jar is down to a single Java-class, containing only a main which calls into Clojure for the rest.
    Thank you very much!

Leave a Reply

%d bloggers like this: