Monday, June 7, 2010

I played around a little with Clojure's Java interoperability at work. Since I made making a new module in the main product that I work on a matter of putting some classes and some xml in a jar file and putting that jar file and any jars it depends on in a directory, it shouldn't be a problem.

The Clojure code was all straightforward, using gen-class. How to actually get the class files generated was all in the documentation, but having the correct classpath was a big snag for me, especially the need to have ./classes in the classpath, which kept causing mysterious NoClassDefFoundErrors. After getting that figured out, I stuck it into ant:

<target name="compile">
<mkdir dir="${build.dir}"/>
<java classname="clojure.main">
<arg value="-e"/>
<arg value="(set! *compile-path* &#34;${build.dir}&#34;) (compile 'name.of.test.ModuleClass)"/>
<classpath>
<fileset dir="${lib.dir}">
<include name="*.jar"/>
</fileset>
<fileset dir="${clojure.home}">
<include name="*.jar"/>
</fileset>
<pathelement location="${src.dir}"/>
<pathelement location="${build.dir}"/>
</classpath>
</java>
</target>

Then, after getting the jar files built, I dropped the jar file and clojure.jar into the directory and got a giant stack trace, leading to

Caused by: java.io.FileNotFoundException: Could not locate clojure/core__init.class or clojure/core.clj on classpath:
at clojure.lang.RT.load(RT.java:402)
at clojure.lang.RT.load(RT.java:371)
at clojure.lang.RT.doInit(RT.java:406)
at clojure.lang.RT.(RT.java:292)
... 45 more

I hadn't the classloader set properly for the initial loading of the modules. For all the other modules, there wasn't much done in the class initializers, so this problem didn't come up. I had the classloader set for all other operations, though, so it was merely a matter of doing the same thing at initialization, and it worked.

I also noted that I handled the classloader by hand-creating a proxy class and implementing each method to wrap the classloader switch,

final ClassLoader handlerClassLoader = handler.getClass().getClassLoader();
return new Handler() {
public Object getObject(Object parameter) {
ClassLoader cl = Thread.currentThread().getContentClassLoader();
Thread.currentThread().setContextClassLoader(handlerClassLoader);
try {
return handler.getObject(parameter);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
};

which I could improve by using java.lang.reflect.Proxy, which I could use to wrap all the calls with one method. The code was about 100 lines shorter, and would no longer have to be changed if the Handler interface changed. I'll check in these changes, but not the test module in Clojure.

No comments:

Post a Comment