Sir Robert
Sir Robert

Reputation: 4946

How can I export a Java class (.jar) from Clojure?

I am relatively novice at Clojure and Java. I have an existing Clojure project someone else wrote that I am trying to embed in NodeJS using node-java.

Clojure

The project defines a namespace that provides certain public functions, like so:

(ns my.namespace
  (:require ...etc...))

(defn dosomething ...)
(defn dosomethingelse ...)

I have built the project with leiningen (lein jar and lein uberjar).

Questions

The #import() docs on node-java say I need to import a java class like so:

const java = require('java');
var Test = java.import('Test');
  1. How can I access these functions (presumably as Java class static methods?)
  2. Am I approaching this all wrong? =)

Update

Thanks to Magos (answer below) I made a little more progress. It turns out I can use (:gen-class :name my.name) in the (ns ...) scope to tell it to generate a class. If I add a profile to the project.clj like so:

...
:profiles {
  ...
  :uberjar {:aot :all}
}
...

It will compile and I can now see the class in Node. I still haven't figured out how to export the methods, though. Working on that part now.

Upvotes: 1

Views: 605

Answers (2)

Sir Robert
Sir Robert

Reputation: 4946

Note: I arrived at this through a combination of Magos's answer and clartaq's comment to the question.

Here are simple instructions for how to do it. Let's assume you have this (simple) clojure code:

(ns my.namespace
  "a trivial library"
  (:require [something.else :as other]))

(defn excite
  "make things more exciting"
  [mystr]
  (print-str mystr "!"))

Use these steps to expose the excite method.

  1. Create an exposed version of the method with the same signature by prefixing it with -. It should simply call the function you wish to expose.

    (defn -excite [mystr] (excite mystr))
    
  2. Declare in (ns ...) that you want to generate a class and export methods.

    (ns my.namespace
      "a trivial library"
      (:require [something.else :as other])
      (:gen-class
        :name my.classname
        :methods [
          ;   metadata      mtd.name signature  returns
          #^{:static true} [excite   [String]   void]
        ]))
    

    Note that you may optionally remove the #^{:static true} if you do not wish to provide this as a static method.

  3. In your project.clj (assuming you are using leiningen), add ahead-of-time compilation instructions to your :profiles entry:

    :profiles {:uberjar {:aot :all}}
    
  4. Compile your uberjar:

    lein uberjar
    

The resulting .jar file will have a class my.classname with a static method excite.

Upvotes: 0

Magos
Magos

Reputation: 3014

Since someone else wrote the Clojure, I'll assume you aren't in control of it. The recommended approach for using Clojure code from another JVM language is bootstrapping from the class clojure.java.api.Clojure. This class allows you to resolve Vars from Clojure, and by resolving and invoking the other core Clojure functions you can load your own code. Based on your example it might look something like this:

const java = require('java');
var clojure = java.import('clojure.java.api.Clojure');
IFn require = clojure.var("clojure.core", "require");
require.invoke(clojure.read("my.namespace"));
IFn doSomething = clojure.var("my.namespace","dosomething");
//doSomething.invoke(....

If you do control the Clojure, :gen-class allows you to export functions as methods of the namespace's generated class.

Upvotes: 3

Related Questions