April 1, 2023
By: Kevin

Java使用Clojure的库

  1. Java通过clojure运行时, 暴露var
  2. Java调用clojure的静态函数

更多情况下, 我们都是在Clojure中调用Java的库.

+--------------+ call +---------------+
|clojure func  +----->| Java  func    |
+--------------+      +---------------+

但是如果我想反过来呢?

+--------------+ call +---------------+
|Java    func  +----->| Clojure func  |
+--------------+      +---------------+

我们想暴露一个clojure的函数给java去使用, 其实有两种方式

  1. 通过暴露Clojure的运行时clojure.lang.RT, 来载入clojure写的源代码, 得到clojure.lang.Var类的实例, 然后通过invoke方法来调用. Java侧使用不太方便, 感觉比较奇怪.
  2. genclass的机制, Clojure代码中声明静态函数, 预先编译, Java中直接import clojure中对应namespace的class, 然后调用这个class的静态函数. 性能高, 使用方法简单, 不会感觉到和调用Java自身函数有什么区别.

所以我们在项目中使用方法2. 1的方法也写在下面, 供参照比较.

Java通过clojure运行时, 暴露var

(ns com.core)

(defn hello [name]
  (str "Hello, " name))
package com;

import clojure.lang.RT;
import clojure.lang.Var;

public class HelloWorld {
    public static void main(String[] args) throws Exception{
        // 载入clojure运行时
        RT.loadResourceScript("com/core.clj");

        // 拿到function对象
        Var foo = RT.var("com.core", "hello");

        // Invoke
        Object result = foo.invoke("World!");
        System.out.println(result);
    }
}

Java调用clojure的静态函数

  • clojrue侧生成静态函数, 注意注释.

    (ns codec-lib.core
      (:gen-class
       :methods [^:static [decode [java.nio.ByteBuffer] Object]]) ;; <= decode 作为静态函数, 指明签名
      (:require
       [codec-lib.frames :as frame]
       [gloss.io :as gio])
    
    (defn -decode                                                 ;; <= -声明为静态函数
      "输入为一个ByteBuffer`bf`, 输出为一个map"
      [bf]
      (let [v  (gio/decode frame/protocol bf)
            b-bfs (:payload-bfs v)
            v'  (gio/decode (frame/the-protocol-body) b-bfs)]
        {:head (:head v)
         :data v'}
        )
      (gio/decode (frame/the-protocol-body) bf))
    
  • Java侧调用方法, 和普通的statci method并无区别

    // Foo.java
    import java.nio.ByteBuffer;
    import codec_lib.core;
    
    public class Foo {
        public static void main(String[] args) throws Exception {
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            Object result = codec_lib.core.decode(byteBuffer);
            System.out.println(result);
        }
    }
    
  • 编译+执行

    • clojure 需要 lein uberjar 生成jar包 生成的两个jar包, 一个是无依赖的, 一个是有依赖的, 简单起见, 我们接下来使用依赖完整的standalone包.

    • java侧需要编译+执行

      # 编译, 需要指定classpath
      javac -cp target/uberjar/codec-lib-0.1.0-SNAPSHOT-standalone.jar  Foo.
      # 运行, 注意claspath最后加上了 `.` 即当前路径也在classpath, 保证Foo可以加载
      java -cp target/uberjar/codec-lib-0.1.0-SNAPSHOT-standalone.jar:.  Foo
      
Tags: clojure Java