Clojure,反思:查找实现接口的类

时间:2018-11-08 14:18:32

标签: java reflection interface clojure classpath

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

  1. 在Java中定义接口
  2. 在Clojure的类中实现它(奖金:带有宏)
  3. 使用类加载器和反射找到它

这是我到目前为止所拥有的:

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))

搜索类别:

(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。如果我搜索类路径中的类(例如,在org.reflections jar中),则此方法有效,但不适用于我先前定义的类,因此,我认为问题不在最后一段代码中,而在前一个,或者可能是用法,例如它需要预编译。

如何在Clojure中定义类,以后可以通过反射找到它们?

2 个答案:

答案 0 :(得分:1)

我对org.reflections不熟悉,但是如果您只想要加载的类列表,则可以使用以下代码获取它:

(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。它只会为您提供已使用与类clojure.main相同的类加载器加载的类,但是如果您没有对类加载器进行任何自定义,那就足够了。

一旦有了该列表,便可以根据需要搜索/过滤它。当然,您要查找的类确实必须先加载。如果不是这种情况,则必须搜索类路径。

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

好的,我知道了,您在问如何创建一个类。首先要说的是,编写Clojure时通常不需要创建命名类,除非您特别想使用某些需要这样做的现有Java代码。如果您要编写纯Clojure,则只需编写函数并直接使用它们即可。

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

  

=>(doc gen-class)

     

clojure.core / gen-class

     

([[&options])

     

     

编译时,使用     给定包限定的:name(作为这些名称中的所有名称     参数,可以是字符串或符号),然后写入.class文件     到 compile-path 目录。不编译时     没有。

因此,您需要编译名称空间。我通常不这样做,所以我不知道是否有一种方法可以不创建.class文件,而直接在内存中创建类,但是如果我正确理解了下面的内容,您就会想要:

(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))

答案 1 :(得分:0)

我不确定您要达到什么目标,但是我可能需要向您指出一些方向。首先,有一个名为clojure.reflect的命名空间,其中包含有用的函数来获取有关类的信息。它使在REPL上反映Java层次结构变得更加容易。其次,诸如clojure.tools.namespace之类的东西将帮助您遍历存储库中的代码并查找实现接口的所有命名空间。但是,除非您要实现诸如Servetlet之类的特定功能,否则,您倾向于使用deftype实现类,而不是使用:gen-class宏中的ns选项之类的AOT编译器功能。