6

I'm REPLed in to a running service, and have a var pointing to a classloader with which a plugin was loaded (with my.package installed).

The DynamicClassLoader used by the REPL does not include the plugin with which I wish to interact; I wish to be able to work with classes loaded from the plugin despite this limitation.

The following works:

=> (.loadClass plugin-classloader "my.package.MyClass")
my.package.MyClass

...whereas the following doesn't (explicitly overriding the thread context classloader):

=> (do
     (.setContextClassLoader (Thread/currentThread) plugin-classloader)
     (Class/forName "my.package.MyClass"))
ClassNotFoundException my.package.MyClass  java.net.URLClassLoader$1.run (URLClassLoader.java:202)

...and neither does this (explicitly overriding the thread context classloader and the clojure.lang.Compiler/LOADER reference):

=> (let [dcl (clojure.lang.DynamicClassLoader. plugin-classloader)]
     (.setContextClassLoader (Thread/currentThread) dcl)
     (with-bindings* {clojure.lang.Compiler/LOADER dcl}
       (eval '(pr-str (Class/forName "my.package.MyClass")))))
ClassNotFoundException my.package.MyClass  java.net.URLClassLoader$1.run (URLClassLoader.java:202)

...and neither does this:

=> my.package.MyClass
CompilerException java.lang.ClassNotFoundException: my.package.MyClass, compiling:(NO_SOURCE_PATH:0)

Shouldn't Class.forName() use the thread context classloader when set? I'm trying to make some calls into 3rd-party code doing introspection magic; the tools in question are failing with ClassNotFoundExceptions even when the thread context classloader should be set.


In the case where I'm explicitly setting the context classloader, the stack trace demonstrates that Clojure's DynamicClassLoader (rather than the BundleClassLoader in the plugin-classloader var) is in use:

=> (e)
java.lang.ClassNotFoundException: my.package.MyClass
 at java.net.URLClassLoader$1.run (URLClassLoader.java:202)
    java.security.AccessController.doPrivileged (AccessController.java:-2)
    java.net.URLClassLoader.findClass (URLClassLoader.java:190)
    clojure.lang.DynamicClassLoader.findClass (DynamicClassLoader.java:61)
    java.lang.ClassLoader.loadClass (ClassLoader.java:306)
    java.lang.ClassLoader.loadClass (ClassLoader.java:247)
    java.lang.Class.forName0 (Class.java:-2)
    java.lang.Class.forName (Class.java:169)
3
  • Seems like eval is playing some trick there... can you try same thing in compiled clojure code? Commented Jun 8, 2012 at 7:18
  • @Ankur - Do you mean AOT-compiled? My understanding is that Clojure's implementation is such that it's all compiled code, even entered at the REPL. Commented Jun 8, 2012 at 12:22
  • @Ankur As it turns out, you were kind of right -- it is compiled code, but the DynamicClassLoader instance bound to clojure.lang.Compiler/LOADER when clojure.lang.Compiler/eval is called matters. Commented Jun 8, 2012 at 19:50

1 Answer 1

6

clojure.lang.Compiler/eval, as called by the REPL, uses clojure.lang.Compiler/LOADER, not the thread-local classloader. An appropriate classloader needs to bound to this var before eval is called -- so adding a layer of wrapping works around this issue:

=> (let [dcl (clojure.lang.DynamicClassLoader. plugin-classloader)]
     (with-bindings {clojure.lang.Compiler/LOADER dcl}
       (eval '(Class/forName "my.package.MyClass"))))
my.package.MyClass
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.