Clojure,反射:找到实现接口的 类
Clojure, reflection: Find classes that implement an interface
这在 Clojure 中似乎比在 Java 和 Scala 中更难。我想做的是:
- 在Java
中定义接口
- 在 Clojure 中的 class 中实现它(奖励:使用宏)
- 使用class加载器和反射
找到它
这是我目前拥有的:
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 之类的。
这在 Clojure 中似乎比在 Java 和 Scala 中更难。我想做的是:
- 在Java 中定义接口
- 在 Clojure 中的 class 中实现它(奖励:使用宏)
- 使用class加载器和反射 找到它
这是我目前拥有的:
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 之类的。