clojure类重装如何工作?

时间:2011-09-19 12:58:02

标签: clojure classloader

我一直在阅读代码和文档,试图了解类重新加载在clojure中是如何工作的。根据许多网站,例如http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html,每当你加载一个类本质上你获得字节码(通过任何数据机制),将字节码转换为类Class的实例(通过defineClass),然后解析(链接)该类通过resolveClass。 (defineClass是否隐式调用resolveClass?)。任何给定的类加载器只允许链接一次类。如果它试图链接现有的类,它什么都不做。这会产生问题,因为您无法链接新实例化的类,因此每次重新加载类时都必须创建类加载器的新实例。

回到clojure,我尝试检查加载类的路径。

在clojure中,您可以根据需要以多种方式定义新类:

匿名类: 具体化 代理

命名类: DEFTYPE defrecord(在引擎盖下使用deftype) 根级

最终,这些代码指向clojure / src / jvm / clojure / lang / DynamicClassLoader.java

其中DynamicClassLoader / defineClass使用super的defineClass创建一个实例,然后缓存该实例。当你想要检索类时,clojure加载调用forName调用类加载器和DynamicClassLoader / findClass,它首先在委托给超类之前查看缓存(这与大多数普通类加载器的工作方式相反,它们在哪里委托第一,而不是自己尝试。)困惑的重点如下:forName被记录为在返回之前链接类,但这意味着你不能从现有的DynamicClassLoader重新加载一个类,而是需要创建一个新的DynamicClassLoader,但我没有在代码中看到这一点。我理解代理和reify定义匿名类,因此它们的名称不同,因此可以被视为不同的类。但是,对于命名类,这会破坏。在真正的clojure代码中,您可以同时引用旧版本的类和对新版本类的引用,但尝试创建新的类实例将是新版本。

请解释clojure如何在不创建DynamicClassLoader的新实例的情况下重新加载类,如果我能理解重新加载类的机制,我想将这个重新加载功能扩展到我可能使用javac创建的java .class文件。

注意: 这个问题涉及类RELOADING,而不仅仅是动态加载。重新加载意味着我已经实习了一个课程但想要实习该实例的新更新版本。

我想重申,尚不清楚clojure如何能够重新加载deftype定义的类。调用deftype最终会调用clojure.lang.DynamicClassLoader / defineClass。再次执行此操作会导致再次调用defineClass,但手动执行此操作会导致链接错误。在这里发生了什么,允许clojure用deftypes做到这一点?

1 个答案:

答案 0 :(得分:31)

并非所有这些语言功能都使用相同的技术。

代理

proxy宏仅根据要继承的接口类和列表生成类名。此类中每个方法的实现委托给存储在对象实例中的Clojure fn。这允许Clojure在每次继承相同的接口列表时使用相同的代理类,无论宏的主体是否相同。没有实际的类重新加载。

具体化

对于reify,方法体直接编译到类中,因此使用的技巧proxy将不起作用。相反,在编译表单时会生成一个新类,因此如果更改表单的主体并重新加载它,则会得到一个全新的类(使用新生成的名称)。所以再次,没有实际的类重新加载。

根级

使用gen-class为生成的类指定名称,因此proxyreify使用的技术都不起作用。 gen-class宏仅包含类的一种规范,但不包含任何方法体。生成的类有点像proxy,遵循方法体的Clojure函数。但由于名称与规范相关联,与proxy不同,它不会更改gen-class的正文并重新加载它,因此gen-class仅在前面编译时可用。时间(AOT编译),不重新启动JVM就不允许重新加载。

deftype和defrecord

这是真正的动态类重新加载的地方。我对JVM的内部并不是很熟悉,但是对调试器和REPL的一点工作清楚地表明:每次需要解析类名时,例如编译使用该类的代码时或者调用类类的forName方法,使用Clojure的DynamicClassLoader/findClass方法。如您所知,这会在DynamicClassLoader的缓存中查找类名,并且可以通过再次运行deftype将其设置为指向新类。

请注意,您提到的关于重新加载的类是另一个类的教程中的警告,尽管名称相同,仍然适用于Clojure类:

(deftype T [a b])  ; define an original class named T
(def x (T. 1 2))   ; create an instance of the original class
(deftype T [a b])  ; load a new class by the same name
(cast T x)         ; cast the old instance to the new class -- fails
; ClassCastException   java.lang.Class.cast (Class.java:2990)

Clojure程序中的每个顶级表单都会获得一个新的DynamicClassLoader,用于该表单中定义的任何新类。这不仅包括通过deftypedefrecord定义的类,还包括reifyfn。这意味着上面的x的类加载器与新的T不同。请注意@之后的数字不同 - 每个都有自己的类加载器:

(.getClassLoader (class x))
;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@337b4703>

(.getClassLoader (class (T. 3 4)))
;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@451c0d60>

但是只要我们不定义新的T类,新实例将具有相同类的类相同的类。请注意@此处的数字与上面的第二个相同:

(.getClassLoader (class (T. 4 5)))
;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@451c0d60>