简体   繁体   English

Clojure - 使用可能不可用的 Java 类编译项目

[英]Clojure - compiling project with Java classes that are potentially not available

I am wrapping a java library in Clojure.我在 Clojure 中包装了一个 java 库。 Depending on the java library version, some classes exist or not, so my library can fail to even compile if it can't find the java classes.根据 java 库版本,某些类是否存在,因此如果找不到 java 类,我的库甚至可能无法编译。 My idea was to use Reflector to use the string name of classes.我的想法是使用 Reflector 来使用类的字符串名称。

Example of what I'm trying to do:我正在尝试做的示例:

(java.time.LocalDateTime/parse "2020-01-01")

would become

(if right-version?
  (clojure.lang.Reflector/invokeStaticMethod "java.time.LocalDate" "parse" (into-array ["2020-01-01"]))

This works but is slower by a factor of 20x.这可行,但速度要慢 20 倍。 Is there a better way to achieve the same?有没有更好的方法来实现同样的目标? Can I use a macro that will define the correct function at compile time, depending on the version of the underlying library?根据底层库的版本,我可以使用在编译时定义正确函数的宏吗?

Thanks,谢谢,

I could not tell from your question if your code uses Reflector on every call to the parse method.我无法从您的问题中判断您的代码是否在每次调用parse方法时都使用Reflector If so, you could instead define the Method once to use later many times:如果是这样,您可以改为定义一次Method以供以后多次使用:

(def right-version? true)   ; set as appropriate

(def ^java.lang.reflect.Method parse-method
  (when right-version?
    (.getMethod (Class/forName "java.time.LocalDateTime")
                "parse"
                (into-array [java.lang.CharSequence]))))

(defn parse-local-date-time [s]
  (when parse-method
    (.invoke parse-method nil (into-array [s]))))
(parse-local-date-time "2020-01-01T14:30:00")
;; => #object[java.time.LocalDateTime 0x268fc120 "2020-01-01T14:30"]

I have been using a macro solution to this problem for 6+ years in the Tupelo Library .我在Tupelo Library中使用宏解决方案已有 6 年多的时间了。 It allows you to write code like:它允许您编写如下代码:

(defn base64-encoder []
  (if-java-1-8-plus
    (java.util.Base64/getEncoder)
    (throw (RuntimeException. "Unimplemented prior to Java 1.8: "))))

The macro itself is quite simple:宏本身非常简单:

     (defmacro if-java-1-11-plus  
       "If JVM is Java 1.11 or higher, evaluates if-form into code. Otherwise, evaluates else-form."
       [if-form else-form]
       (if (is-java-11-plus?)
         `(do ~if-form)
         `(do ~else-form)))

     (defmacro when-java-1-11-plus  
       "If JVM is Java 1.11 or higher, evaluates forms into code. Otherwise, elide forms."
       [& forms]
       (when (is-java-11-plus?)
         `(do ~@forms)))

and the version testing functions look like版本测试功能看起来像

     ;-----------------------------------------------------------------------------
     ; Java version stuff
     (s/defn version-str->semantic-vec :- [s/Int]
       "Returns the java version as a semantic vector of integers, like `11.0.17` => [11 0 17]"
       [s :- s/Str]
       (let [v1 (str/trim s)
             v2 (xsecond (re-matches #"([.0-9]+).*" v1)) ; remove any suffix like on `1.8.0-b097` or `1.8.0_234`
             v3 (str/split v2 #"\.")
             v4 (mapv #(Integer/parseInt %) v3)]
         v4))

     (s/defn java-version-str :- s/Str
       [] (System/getProperty "java.version"))

     (s/defn java-version-semantic :- [s/Int]
       [] (version-str->semantic-vec (java-version-str)))

     (s/defn java-version-min? :- s/Bool
       "Returns true if Java version is at least as great as supplied string.
       Sort is by lexicographic (alphabetic) order."
       [tgt-version-str :- s/Str]
       (let [tgt-version-vec    (version-str->semantic-vec tgt-version-str)
             actual-version-vec  (java-version-semantic)
             result             (increasing-or-equal? tgt-version-vec actual-version-vec)]
         result))

    (when-not (java-version-min? "1.7")
      (throw (ex-info "Must have at least Java 1.7" {:java-version (java-version-str)})))


     (defn is-java-8-plus? [] (java-version-min? "1.8")) ; ***** NOTE: version string is still `1.8` *****
     (defn is-java-11-plus? [] (java-version-min? "11"))  
     (defn is-java-17-plus? [] (java-version-min? "17"))  

The advantage of using the macro version is that you can refer to a Java class normally via the symbol java.util.Base64 .使用宏版本的优点是您可以通过符号java.util.Base64正常引用 Java 类。 Without macros, this will crash the compiler for older versions of Java even if wrapped by an if or when , since the symbol will be unresolved before the if or when is evaluated.如果没有宏,即使被ifwhen包裹,旧版本 Java 的编译器也会崩溃,因为在评估ifwhen之前符号将无法解析。

Since Java doesn't have macros, the only workaround in that case is to use the string "java.util.Base64" and then Class/forName , etc, which is awkward & ugly.由于 Java 没有宏,因此在这种情况下唯一的解决方法是使用字符串"java.util.Base64" ,然后使用Class/forName等,这既尴尬又丑陋。 Since Clojure has macros, we can take advantage of conditional code compilation to avoid needing the powerful (but awkward) Java Reflection API.由于 Clojure 有宏,我们可以利用条件代码编译来避免需要强大(但笨拙)的 Java 反射 API。

Instead of copying or re-writing these functions into your own code, just use put无需将这些函数复制或重写到您自己的代码中,只需使用 put

[tupelo "22.05.04"]

into your project.clj and away you go!进入你的project.clj ,然后你就走了!


PS附言

You do not need to throw an exception if you detect an older version of Java.如果检测到较旧版本的 Java,则无需引发异常。 This example simply elides the code if the Java version is too old:如果 Java 版本太旧,此示例将简单地省略代码:

(t/when-java-1-11-plus

  (dotest
    (throws-not? (Instant/parse "2019-02-14T02:03:04.334Z"))
    (throws-not? (Instant/parse "2019-02-14T02:03:04Z"))
    (throws-not? (Instant/parse "0019-02-14T02:03:04Z")) ; can handle really old dates w/o throwing

...)

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

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