gpt4 book ai didi

clojure - Clojure 类重载是如何工作的?

转载 作者:行者123 更新时间:2023-12-02 07:06:35 25 4
gpt4 key购买 nike

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

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

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

匿名类:具体化代理

命名类:定义类型defrecord(在底层使用 deftype)世代级

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

其中DynamicClassLoader/defineClass使用super的defineClass创建一个实例,然后缓存该实例。当您想要检索类时,clojure 通过调用 forName 来加载,它调用类加载器和 DynamicClassLoader/findClass,它在委托(delegate)给父类(super class)之前首先在缓存中查找(这与大多数普通类加载器的工作方式相反,它们在首先委托(delegate),而不是自己尝试。) 重要的混淆点如下:forName 被记录为在返回之前链接该类,但这意味着您无法从现有的 DynamicClassLoader 重新加载类,而是需要创建一个新的 DynamicClassLoader,但是我在代码中没有看到这一点。 我知道 proxy 和 reify 定义了匿名类,因此它们的名称不同,因此可以将其视为不同的类。然而,对于指定的类,这种情况就失效了。在真正的 clojure 代码中,您可以同时引用旧版本的类和新版本的类,但尝试创建新的类实例将是新版本的。

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

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

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

最佳答案

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

代理

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

具体化

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

一代

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

deftype 和 defrecord

这是真正的动态类重新加载发生的地方。我对 JVM 的内部结构不是很熟悉,但是使用调试器和 REPL 进行一些工作可以清楚地表明一点:每次需要解析类名时,例如编译使用该类的代码时或当调用Class类的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>

关于clojure - Clojure 类重载是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7471316/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com