Clojure,反射:找到实现接口的 类

Clojure, reflection: Find classes that implement an interface

这在 Clojure 中似乎比在 Java 和 Scala 中更难。我想做的是:

  1. 在Java
  2. 中定义接口
  3. 在 Clojure 中的 class 中实现它(奖励:使用宏)
  4. 使用class加载器和反射
  5. 找到它

这是我目前拥有的:

Java界面

package hello.interfaces;
public interface Test {
    String doSomething(String input);
}

Clojure 定义

(ns hello.impl.test
  (:gen-class
    :implements [hello.interfaces.Test]))

(defn doSomething [v] (str "hello " v))

正在搜索 classes:

(ns hello.core
  (:import
    (org.reflections Reflections)
    (org.reflections.util ConfigurationBuilder))
  (:gen-class))

(defn -instances []
  (let [paths (into-array Object ["hello"])]
      (.getSubTypesOf (Reflections. paths) hello.interfaces.Test)))

(defn -main [& args]
  (println (-instances)))

我正在使用 org.reflections。如果我搜索 class 路径中的 classes(例如在 org.reflections jar 中),这会起作用,但它不适用于我之前定义的 class,因此我认为问题不在最后一段代码中,而是在前一段代码中,或者可能在使用中,例如它需要预编译它。

如何在 Clojure 中定义 classes 以便稍后通过反射找到?

我不熟悉org.reflections,但是如果你只是想要一个加载的class列表,你可以用下面的代码得到它:

(let [classloader (.getClassLoader clojure.main)
      classes-field (.getDeclaredField java.lang.ClassLoader "classes")]
  (.setAccessible classes-field true)
  (let [class-list (.get classes-field classloader)
        class-vec (reduce conj [] class-list)] ; copy everything into a new vector rather than working directly with the classloader's private field
    class-vec))

听起来你对Java很熟悉,所以我猜你可以看到上面基本上只是翻译Java。它只会给你 classes 已经加载了与 class clojure.main 相同的 class 加载器,但是如果你没有用你的 class 装载机,应该足够了。

获得该列表后,您可以 search/filter 随心所欲。当然,您要查找的 class 必须首先加载。如果不是这种情况,您将不得不搜索 class 路径。

===更新以回复您的评论===

好的,我明白了,你问的是如何创建 class。首先要说的是,您在编写 Clojure 时通常不需要创建命名的 classes,除非您特别想使用一些现有的 Java 代码,这需要您这样做。如果您正在编写纯 Clojure,您只需编写函数并直接使用它们。

但是,您当然可以这样做。 gen-class 文档的第一部分指出:

=> (doc gen-class)

clojure.core/gen-class

([& options])

Macro

When compiling, generates compiled bytecode for a class with the given package-qualified :name (which, as all names in these parameters, can be a string or symbol), and writes the .class file to the compile-path directory. When not compiling, does nothing.

因此,您需要编译命名空间。我通常不这样做,所以我不知道是否有一种方法可以不创建 .class 文件,而是直接在内存中创建 classes,但下面是你做的想要,如果我没听错的话:

(ns wip.test)

; looks for wip/himplement.clj on the classpath, and compiles it into .class files
; requires that ../bin is in the classpath
(binding [*compile-path* "../bin"]
     (compile 'wip.himplement))

; loads the wip.himplement class from the .class files
(Class/forName "wip.himplement")


; create a list of all loaded classes (could presumably also be done with org.reflections)
(def classes-list (let [classloader (.getClassLoader clojure.main)
                        classes-field (.getDeclaredField java.lang.ClassLoader "classes")]
                    (.setAccessible classes-field true)
                    (java.util.ArrayList. (.get classes-field classloader))))

; Outputs all loaded classes which implement HInterface. Output is:
; (wip.hello.HInterface wip.himplement)
(println (filter #(isa? % wip.hello.HInterface) classes-list))

我不完全确定您要实现的目标,但我可能应该为您指明一些方向。首先有一个名为 clojure.reflect 的名称空间,其中包含用于获取有关 类 的信息的有用函数。它可以更容易地在 REPL 中反映 Java 层次结构。其次,像 clojure.tools.namespace 这样的东西可以帮助你遍历你的 repo 中的代码并找到所有实现接口的命名空间。但是,您倾向于使用 deftype 来实现 类 而不是使用 AOT 编译器功能,例如 ns 宏中的 :gen-class 选项,除非您正在实现特定的东西,例如servelet 之类的。