简体   繁体   English

从Java调用Clojure

[英]Calling clojure from java

Most of the top google hits for "calling clojure from java" are outdated and recommend using clojure.lang.RT to compile the source code. 对于“从Java调用clojure”而言,大多数Google热门歌曲都已过时,建议您使用clojure.lang.RT来编译源代码。 Could you help with a clear explanation of how to call Clojure from Java assuming you have already built a jar from the Clojure project and included it in the classpath? 假设您已经从Clojure项目构建了一个jar并将其包含在类路径中,那么您能否帮助您清楚地说明如何从Java调用Clojure?

Update : Since this answer was posted, some of the tools available have changed. 更新 :自发布此答案以来,某些可用工具已更改。 After the original answer, there is an update including information on how to build the example with current tools. 原始答案之后,进行了更新,其中包含有关如何使用当前工具构建示例的信息。

It isn't quite as simple as compiling to a jar and calling the internal methods. 它不像编译成jar并调用内部方法那样简单。 There do seem to be a few tricks to make it all work though. 确实有一些技巧可以使它们全部起作用。 Here's an example of a simple Clojure file that can be compiled to a jar: 这是可以编译为jar的简单Clojure文件的示例:

(ns com.domain.tiny
  (:gen-class
    :name com.domain.tiny
    :methods [#^{:static true} [binomial [int int] double]]))

(defn binomial
  "Calculate the binomial coefficient."
  [n k]
  (let [a (inc n)]
    (loop [b 1
           c 1]
      (if (> b k)
        c
        (recur (inc b) (* (/ (- a b) b) c))))))

(defn -binomial
  "A Java-callable wrapper around the 'binomial' function."
  [n k]
  (binomial n k))

(defn -main []
  (println (str "(binomial 5 3): " (binomial 5 3)))
  (println (str "(binomial 10042 111): " (binomial 10042 111)))
)

If you run it, you should see something like: 如果运行它,应该会看到类似以下内容的内容:

(binomial 5 3): 10
(binomial 10042 111): 49068389575068144946633777...

And here's a Java program that calls the -binomial function in the tiny.jar . 而这里的调用Java程序-binomial在功能tiny.jar

import com.domain.tiny;

public class Main {

    public static void main(String[] args) {
        System.out.println("(binomial 5 3): " + tiny.binomial(5, 3));
        System.out.println("(binomial 10042, 111): " + tiny.binomial(10042, 111));
    }
}

It's output is: 它的输出是:

(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

The first piece of magic is using the :methods keyword in the gen-class statement. 魔术的第一步是在gen-class语句中使用:methods关键字。 That seems to be required to let you access the Clojure function something like static methods in Java. 似乎需要这样做才能让您访问Clojure函数,例如Java中的静态方法。

The second thing is to create a wrapper function that can be called by Java. 第二件事是创建一个可由Java调用的包装器函数。 Notice that the second version of -binomial has a dash in front of it. 注意,第二个版本的-binomial前面带有一个破折号。

And of course the Clojure jar itself must be on the class path. 当然,Clojure jar本身必须位于类路径上。 This example used the Clojure-1.1.0 jar. 本示例使用Clojure-1.1.0 jar。

Update : This answer has been re-tested using the following tools: 更新 :已使用以下工具重新测试了此答案:

  • Clojure 1.5.1 Clojure 1.5.1
  • Leiningen 2.1.3 莱宁根2.1.3
  • JDK 1.7.0 Update 25 JDK 1.7.0更新25

The Clojure Part Clojure部分

First create a project and associated directory structure using Leiningen: 首先使用Leiningen创建一个项目和相关的目录结构:

C:\projects>lein new com.domain.tiny

Now, change to the project directory. 现在,转到项目目录。

C:\projects>cd com.domain.tiny

In the project directory, open the project.clj file and edit it such that the contents are as shown below. 在项目目录中,打开project.clj文件并对其进行编辑,以使内容如下所示。

(defproject com.domain.tiny "0.1.0-SNAPSHOT"
  :description "An example of stand alone Clojure-Java interop"
  :url "http://clarkonium.net/2013/06/java-clojure-interop-an-update/"
  :license {:name "Eclipse Public License"
  :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]]
  :aot :all
  :main com.domain.tiny)

Now, make sure all of the dependencies (Clojure) are available. 现在,确保所有依赖项(Clojure)都可用。

C:\projects\com.domain.tiny>lein deps

You may see a message about downloading the Clojure jar at this point. 此时您可能会看到一条有关下载Clojure jar的消息。

Now edit the Clojure file C:\\projects\\com.domain.tiny\\src\\com\\domain\\tiny.clj such that it contains the Clojure program shown in the original answer. 现在,编辑Clojure文件C:\\projects\\com.domain.tiny\\src\\com\\domain\\tiny.clj ,使其包含原始答案中显示的Clojure程序。 (This file was created when Leiningen created the project.) (此文件是在Leiningen创建项目时创建的。)

Much of the magic here is in the namespace declaration. 这里的许多魔术都在名称空间声明中。 The :gen-class tells the system to create a class named com.domain.tiny with a single static method called binomial , a function taking two integer arguments and returning a double. :gen-class告诉系统使用一个名为binomial静态方法创建一个名为com.domain.tiny的类,该方法接受两个整数参数并返回一个double。 There are two similarly named functions binomial , a traditional Clojure function, and -binomial and wrapper accessible from Java. 有两个相似命名的函数binomial ,传统的Clojure函数以及-binomial和可从Java访问的包装器。 Note the hyphen in the function name -binomial . 注意函数名称-binomial的连字符。 The default prefix is a hyphen, but it can be changed to something else if desired. 默认前缀是连字符,但是如果需要,可以将其更改为其他名称。 The -main function just makes a couple of calls to the binomial function to assure that we are getting the correct results. -main函数仅对二项式函数进行了两次调用,以确保获得正确的结果。 To do that, compile the class and run the program. 为此,请编译类并运行程序。

C:\projects\com.domain.tiny>lein run

You should see output shown in the original answer. 您应该看到原始答案中显示的输出。

Now package it up in a jar and put it someplace convenient. 现在将其包装在罐子中,并放在方便的地方。 Copy the Clojure jar there too. 也将Clojure罐子复制到那里。

C:\projects\com.domain.tiny>lein jar
Created C:\projects\com.domain.tiny\target\com.domain.tiny-0.1.0-SNAPSHOT.jar
C:\projects\com.domain.tiny>mkdir \target\lib

C:\projects\com.domain.tiny>copy target\com.domain.tiny-0.1.0-SNAPSHOT.jar target\lib\
        1 file(s) copied.

C:\projects\com.domain.tiny>copy "C:<path to clojure jar>\clojure-1.5.1.jar" target\lib\
        1 file(s) copied.

The Java Part Java部分

Leiningen has a built-in task, lein-javac , that should be able to help with the Java compilation. Leiningen有一个内置任务lein-javac ,应该可以帮助进行Java编译。 Unfortunately, it seems to be broken in version 2.1.3. 不幸的是,它似乎在2.1.3版本中已被破坏。 It can't find the installed JDK and it can't find the Maven repository. 它找不到已安装的JDK,也找不到Maven存储库。 The paths to both have embedded spaces on my system. 两者的路径在我的系统上都有嵌入式空间。 I assume that is the problem. 我认为这就是问题所在。 Any Java IDE could handle the compilation and packaging too. 任何Java IDE都可以处理编译和打包。 But for this post, we're going old school and doing it at the command line. 但是对于这篇文章,我们要上老派,并在命令行中进行。

First create the file Main.java with the contents shown in the original answer. 首先使用原始答案中显示的内容创建文件Main.java

To compile java part 编译java部分

javac -g -cp target\com.domain.tiny-0.1.0-SNAPSHOT.jar -d target\src\com\domain\Main.java

Now create a file with some meta-information to add to the jar we want to build. 现在创建一个包含一些元信息的文件,将其添加到我们要构建的jar中。 In Manifest.txt , add the following text Manifest.txt ,添加以下文本

Class-Path: lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
Main-Class: Main

Now package it all up into one big jar file, including our Clojure program and the Clojure jar. 现在将其打包到一个大的jar文件中,包括我们的Clojure程序和Clojure jar。

C:\projects\com.domain.tiny\target>jar cfm Interop.jar Manifest.txt Main.class lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar

To run the program: 要运行程序:

C:\projects\com.domain.tiny\target>java -jar Interop.jar
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

The output is essentially identical to that produced by Clojure alone, but the result has been converted to a Java double. 输出基本上与Clojure单独产生的输出相同,但是结果已转换为Java double。

As mentioned, a Java IDE will probably take care of the messy compilation arguments and the packaging. 如前所述,Java IDE可能会处理混乱的编译参数和包装。

As of Clojure 1.6.0, there is a new preferred way to load and invoke Clojure functions. 从Clojure 1.6.0开始,有一种加载和调用Clojure函数的新的首选方法。 This method is now preferred to calling RT directly (and supersedes many of the other answers here). 现在,此方法优于直接调用RT(并在此取代了许多其他答案)。 The javadoc is here - the main entry point is clojure.java.api.Clojure . Javadoc在这里 -主要入口点是clojure.java.api.Clojure

To lookup and call a Clojure function: 要查找并调用Clojure函数:

IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(1, 2);

Functions in clojure.core are automatically loaded. clojure.core中的函数会自动加载。 Other namespaces can be loaded via require: 其他名称空间可以通过require加载:

IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("clojure.set"));

IFn s can be passed to higher order functions, eg the example below passes plus to read : IFn可以传递给高阶函数,例如,下面的示例传递plusread

IFn map = Clojure.var("clojure.core", "map");
IFn inc = Clojure.var("clojure.core", "inc");
map.invoke(inc, Clojure.read("[1 2 3]"));

Most IFn s in Clojure refer to functions. Clojure中的大多数IFn引用函数。 A few, however, refer to non-function data values. 但是,有少数是指非功能数据值。 To access these, use deref instead of fn : 要访问这些,请使用deref而不是fn

IFn printLength = Clojure.var("clojure.core", "*print-length*");
IFn deref = Clojure.var("clojure.core", "deref");
deref.invoke(printLength);

Sometimes (if using some other part of the Clojure runtime), you may need to ensure that the Clojure runtime is properly initialized - calling a method on the Clojure class is sufficient for this purpose. 有时(如果使用Clojure运行时的其他部分),您可能需要确保Clojure运行时已正确初始化-为此,在Clojure类上调用方法就足够了。 If you do not need to call a method on Clojure, then simply causing the class to load is sufficient (in the past there has been a similar recommendation to load the RT class; this is now preferred): 如果您不需要在Clojure上调用方法,则只需使类加载就足够了(过去曾经有类似的建议来加载RT类;现在首选):

Class.forName("clojure.java.api.Clojure") 

EDIT This answer was written in 2010, and worked at that time. 编辑此答案写于2010年,并在当时起作用。 See Alex Miller's answer for more modern solution. 有关更现代的解决方案,请参见Alex Miller的答案。

What kind of code are calling from Java? 从Java调用什么样的代码? If you have class generated with gen-class, then simply call it. 如果您使用gen-class生成了类,则只需调用它即可。 If you want to call function from script, then look to following example . 如果要从脚本调用函数,请参见以下示例

If you want to evaluate code from string, inside Java, then you can use following code: 如果要在Java内部从字符串求值代码,则可以使用以下代码:

import clojure.lang.RT;
import clojure.lang.Var;
import clojure.lang.Compiler;
import java.io.StringReader;

public class Foo {
  public static void main(String[] args) throws Exception {
    // Load the Clojure script -- as a side effect this initializes the runtime.
    String str = "(ns user) (defn foo [a b]   (str a \" \" b))";

    //RT.loadResourceScript("foo.clj");
    Compiler.load(new StringReader(str));

    // Get a reference to the foo function.
    Var foo = RT.var("user", "foo");

    // Call it!
    Object result = foo.invoke("Hi", "there");
    System.out.println(result);
  }
}

EDIT: I wrote this answer almost three years ago. 编辑:我差不多三年前写了这个答案。 In Clojure 1.6 there is a proper API exactly for the purpose of calling Clojure from Java. 在Clojure 1.6中,有一个正确的API正是为了从Java调用Clojure。 Please Alex Miller's answer for up to date information. Alex Miller的答案以获取最新信息。

Original answer from 2011: 2011年的原始答案:

As I see it, the simplest way (if you don't generate a class with AOT compilation) is to use clojure.lang.RT to access functions in clojure. 如我所见,最简单的方法(如果不使用AOT编译生成类)是使用clojure.lang.RT访问clojure中的函数。 With it you can mimic what you would have done in Clojure (no need to compile things in special ways): 有了它,您可以模仿您在Clojure中所做的事情(无需以特殊方式编译内容):

;; Example usage of the "bar-fn" function from the "foo.ns" namespace from Clojure
(require 'foo.ns)
(foo.ns/bar-fn 1 2 3)

And in Java: 在Java中:

// Example usage of the "bar-fn" function from the "foo.ns" namespace from Java
import clojure.lang.RT;
import clojure.lang.Symbol;
...
RT.var("clojure.core", "require").invoke(Symbol.intern("foo.ns"));
RT.var("foo.ns", "bar-fn").invoke(1, 2, 3);

It is a bit more verbose in Java, but I hope it's clear that the pieces of code are equivalent. 在Java中,它有些冗长,但是我希望很明显,代码片段是等效的。

This should work as long as Clojure and the source files (or compiled files) of your Clojure code is on the classpath. 只要Clojure和Clojure代码的源文件(或编译文件)在类路径上,此方法就可以工作。

I agree with clartaq's answer, but I felt that beginners could also use: 我同意clartaq的回答,但我认为初学者也可以使用:

  • step-by-step information on how to actually get this running 有关如何实际运行此程序的分步信息
  • information that's current for Clojure 1.3 and recent versions of leiningen. Clojure 1.3和最新版本的leiningen的当前信息。
  • a Clojure jar that also includes a main function, so it can be run standalone or linked as a library. 一个Clojure jar,它也包含一个主要功能,因此可以独立运行作为库链接。

So I covered all that in this blog post . 因此,我在这篇博客文章中介绍了所有内容。

The Clojure code looks like this: Clojure代码如下所示:

(ns ThingOne.core
 (:gen-class
    :methods [#^{:static true} [foo [int] void]]))

(defn -foo [i] (println "Hello from Clojure. My input was " i))

(defn -main [] (println "Hello from Clojure -main." ))

The leiningen 1.7.1 project setup looks like this: leiningen 1.7.1项目设置如下所示:

(defproject ThingOne "1.0.0-SNAPSHOT"
  :description "Hello, Clojure"
  :dependencies [[org.clojure/clojure "1.3.0"]]
  :aot [ThingOne.core]
  :main ThingOne.core)

The Java code looks like this: Java代码如下所示:

import ThingOne.*;

class HelloJava {
    public static void main(String[] args) {
        System.out.println("Hello from Java!");
        core.foo (12345);
    }
}

Or you can also get all the code from this project on github . 或者,您也可以在github上该项目中获取所有代码。

This works with Clojure 1.5.0: 这适用于Clojure 1.5.0:

public class CljTest {
    public static Object evalClj(String a) {
        return clojure.lang.Compiler.load(new java.io.StringReader(a));
    }

    public static void main(String[] args) {
        new clojure.lang.RT(); // needed since 1.5.0        
        System.out.println(evalClj("(+ 1 2)"));
    }
}

If the use case is to include a JAR built with Clojure in a Java application, I have found having a separate namespace for the interface between the two worlds to be beneficial: 如果用例是在Java应用程序中包括使用Clojure构建的JAR,我发现为两个世界之间的接口提供单独的命名空间将是有益的:

(ns example-app.interop
  (:require [example-app.core :as core])

;; This example covers two-way communication: the Clojure library 
;; relies on the wrapping Java app for some functionality (through
;; an interface that the Clojure library provides and the Java app
;; implements) and the Java app calls the Clojure library to perform 
;; work. The latter case is covered by a class provided by the Clojure lib.
;; 
;; This namespace should be AOT compiled.

;; The interface that the java app can implement
(gen-interface
  :name com.example.WeatherForecast
  :methods [[getTemperature [] Double]])

;; The class that the java app instantiates
(gen-class
  :name com.example.HighTemperatureMailer
  :state state
  :init init
  ;; Dependency injection - take an instance of the previously defined
  ;; interface as a constructor argument
  :constructors {[com.example.WeatherForecast] []}
  :methods [[sendMails [] void]])

(defn -init [weather-forecast]
  [[] {:weather-forecast weather-forecast}])

;; The actual work is done in the core namespace
(defn -sendMails
  [this]
  (core/send-mails (.state this)))

The core namespace can use the injected instance to accomplish its tasks: 核心名称空间可以使用注入的实例来完成其任务:

(ns example-app.core)

(defn send-mails 
  [{:keys [weather-forecast]}]
  (let [temp (.getTemperature weather-forecast)] ...)) 

For testing purposes, the interface can be stubbed: 出于测试目的,可以对接口进行打桩:

(example-app.core/send-mails 
  (reify com.example.WeatherForecast (getTemperature [this] ...)))

在JVM之上还可以与其他语言一起使用的其他技术是,为要调用的函数声明一个接口,然后使用“代理”函数创建实现它们的实例。

You can also use AOT compilation to create class files representing your clojure code. 您还可以使用AOT编译来创建代表Clojure代码的类文件。 Read the documentation about compilation, gen-class and friends in the Clojure API docs for the details about how to do this, but in essence you will create a class that calls clojure functions for each method invocation. 请阅读Clojure API文档中有关编译,gen-class和朋友的文档,以获取有关如何执行此操作的详细信息,但从本质上讲,您将创建一个类,该类为每次方法调用均调用clojure函数。

Another alternative is to use the new defprotocol and deftype functionality, which will also require AOT compilation but provide better performance. 另一种选择是使用新的defprotocol和deftype功能,这也将需要AOT编译,但可以提供更好的性能。 I don't know the details of how to do this yet, but a question on the mailing list would probably do the trick. 我尚不知道如何执行此操作的详细信息,但是邮件列表中的问题可能可以解决问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM