clojure实战——如何在java中调用clojure函数

一、构建clojure工程

clojure工程的构建在此不详细讲述。假设你clojure工程中创建了如下单元:

(ns holiday.holiday-client)

(defn holiday-status
  [game-id type]
  (let [cfg (util/load-config-memo "holiday-config.edn")]
  "holiday-status is called"))

(defn holiday-rewards
  [game-id type users datas]
  1)

在函数中holiday-status我们加载了一个holiday-config.edn配置文件,返回一个字符串。其中load-config-memo函数会将加载的配置文件内容缓存在内存中,这样以后再调用该函数时,就不要重新读取配置文件了,它是基于clojure的memoize函数实现的;在函数holiday-rewards中,返回了一个整数1。上述两个函数是想暴露给java调用的,下面讲java工程中如何调用它们。

二、引入clojure包

将上述构建的clojure工程打包,假设包名为holiday-client-0.1.0-standalone.jar,以mvn构建的项目,在pom.xml中加入如下配置,引入clojure库以及上述holiday-client-0.1.0-standalone.jar包。clojure工程的配置文件,则复制到java工程的资源目录下即可。

<dependency>
    <groupId>org.clojuregroupId>
    <artifactId>clojureartifactId>
    <version>1.6.0version>
dependency>
<dependency>
    <groupId>holiday-clientgroupId>
    <artifactId>holiday-clientartifactId>
    <version>0.1.0-standaloneversion>
dependency>

注意:对于lein构建的clojure工程,用lein uberjar命令打jar包时,会打出两种包:holiday-client-0.1.0.jarholiday-client-0.1.0-standalone.jar。第一种jar包是不包含clojure工程所依赖的其他包的,因此如果你在pom.xml文件中引入的是这种包,那么连带它依赖的其他包也要一起引入。第二种*-standalone.jar包,是将其所以来的其他包一并打入到这个独立包中,你只需在java工程中单独引入这个包就行,但这可能会出现一个问题:该独立包中依赖的其他库,可能在java工程中已经存在另外一个版本,这时就可能产生冲突(比如clojure工程找到的包是java中的那个版本,从而产生bug),这种bug在不同的系统环境中可能不同,比如在测试环境中不会暴露(因为找到了正确的包),但在发布环境中就出现T T。

三、建立java与clojure交换的工具类

以下是我在实际项目中提取出来的用于与clojure交互的工具类:

package holidaycarnival;

import clojure.java.api.Clojure;
import clojure.lang.IFn;

/**
 * Java跟Clojure交互的工具方法
 *
 */
public class CljUtil {
    private static final IFn require = Clojure.var("clojure.core", "require");

    /**
     * 加载指定的Clojure空间 使用相应空间的函数前必须先调用该函数加载相应空间
     *
     * @param ns
     */
    public static final void requireNameSpace(String ns) {
        require.invoke(Clojure.read(ns));
    }

    /**
     * 引用指定空间中的相应Clojure函数 注意:先加载相应函数
     */
    public static final IFn referClojureFn(String ns, String fn) {
        return Clojure.var(ns, fn);
    }
}

接下来,可以定义一个与业务相关的用于调用clojure工程函数的类:

package holidaycarnival;

import clojure.lang.IFn;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONValue;

public class CljHoliday {

   /**
    * cljNsHoliday是我们想要调用的函数所在的clojure命名空间
    */
    private static final String cljNsHoliday = "holiday.holiday-client";
   /**
    * 业务相关的其他数据
    */
    public static final  String DOUBLE_EXP = "double-exp";

   /**
    * 加载clojure命名空间
    */
    static {
        CljUtil.requireNameSpace(cljNsHoliday);
    }

    /**
     * 获取假期活动奖励(调用clojure工程中的holiday-rewards函数)
     */
    public static Integer[] holidayRewards(String gameID, String type, String[] usernames, int[] datas) {
        IFn cljHolidayRewards = CljUtil.referClojureFn(cljNsHoliday, "holiday-rewards");
        return (Integer[])cljHolidayRewards.invoke(gameID, type, usernames, datas);
    }

    /**
     * 查询当前假期活动是否开启
     * 返回一个json对象,内容为:
     * { status : String 当前活动状态close/open
         remain : Long 距离活动开始/结束最少剩余1000(mills)
           type : String 活动类型:双倍经验“double-exp”
           data : double 活动数据:如在双倍经验活动中2.0表示经验奖励倍数
            msg : String  附加消息}
     */
    public static JSONObject holidayStatus(String gameID, String type){
        IFn cljIsHolidayStatus = CljUtil.referClojureFn(cljNsHoliday, "holiday-status");
        Object status = cljIsHolidayStatus.invoke(gameID, type);
        JSONObject jsonObject = (JSONObject) JSONValue.parse(status.toString());
        return jsonObject;
    }
}

这样java工程中就可以通过CljHoliday的静态函数,来调用clojure函数了。

四、总结

clojure作为一门函数式编程语言,有其自身的优势,比如可以用极为精简的代码写一些逻辑运算,灵活的edn文件配置,编程效率特别高。因此,在需要编写一些较为复杂的逻辑模块或者是要和第三方clojure库/服务进行无缝交互时,可以使用上述的方法。

你可能感兴趣的:(clojure实践积累)