Brent Millare <brent.mill...@gmail.com> writes:

> Can someone please elaborate on this subclassloader process? Although
> the design for leiningen is architecturally simple and understandable
> and thus easy to extend, I'm still quite unfamiliar with the "java
> way". I believe a little documentation on this aspect can make a
> significant improvement in allowing contributions to leiningen.

Given I implemented the initial support for it, I'll give it a go.  In
earlier versions of Leiningen you weren't able to compile code against a
different version of Clojure to the one that Lein was running on top
of, as you could only have one or the other in the classpath.  One way to
get around this is to fork off a new java process and run the compile in
a separate JVM.  Unfortunately we'd then be incurring the JVM's startup
overhead twice (and lein is already annoyingly slow to start).  Wouldn't
it be nice if we could just run the compile in the original JVM but
isolated in a container somehow?

Well we can actually do this because the JVM supports custom
classloaders.  A classloader is an object that when given a classname,
locates the appropriate class files (in jars, classpath etc) and loads it
into memory.  There's a default builtin classloader which searches the
classpath you supply on the command-line. Custom classloaders can be
used for a number of things, for example loading jars over the network
or out of a new archive format.  They also play a strong role in the
JVM's security module (for sandboxing etc).  When a class is loaded
(manually) into a custom classloader, any classes it in turn 
tries to load (by normal means) are also loaded via that same
classloader.

So basically what Lein does is creates a new classloader (it actually
reuses Apache Ant's implementation) with a different classpath which
points to the version of clojure and other libraries that you want to
compile against. 

Take a look at the eval-in-project function in leiningen/compile.clj.
You pass in a form to be evaluated as an argument.  It's converted to a
string and then clojure.main is invoked with the '-e' argument to
evaluate the string 'inside' the new classloader.  The reason its done
this way is so that its easy to fall back to forking and also because
objects in the new classloader aren't compatible with those in the
original (as they're potentially different versions of Clojure).

You can see that it doesn't work perfectly for all cases, so for example
if you're using native libs or OS X it'll fork instead.  (OS X injects
some GUI stuff into the classpath of all the apps it runs, so anything
using using Swing or AWT doesn't work properly in the new classloader).

You may have wondered how Java servlet containers like Jetty and Tomcat
are able to load different versions of the same class into the same JVM
without them conflicting.  As I understand it they use basically the
same mechanism.

Hope that helps.  If you're having trouble following the classloader
stuff just think of the eval-in-project function as starting a new
"virtual" JVM with a different classpath and evaling some code in it.
Its really not all that complex, its just the terminology makes it sound
somewhat arcane.

Alex

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

To unsubscribe from this group, send email to 
clojure+unsubscribegooglegroups.com or reply to this email with the words 
"REMOVE ME" as the subject.

Reply via email to