Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class/forName in Clojure not respecting ContextClassLoader?

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)
like image 834
Charles Duffy Avatar asked Jun 07 '12 23:06

Charles Duffy


1 Answers

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
like image 141
Charles Duffy Avatar answered Oct 29 '22 07:10

Charles Duffy