clojure 类重新加载如何工作? [英] How does clojure class reloading work?

查看:15
本文介绍了clojure 类重新加载如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

匿名类:物化代理

命名类:定义类型defrecord(在幕后使用 deftype)gen类

最终,这些代码指向 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 来做到这一点?

解决方案

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

代理

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

具体化

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

gen-class

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

定义类型和取消记录

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

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

(deftype T [a b]) ;定义一个名为 T 的原始类(def x (T. 1 2)) ;创建原始类的实例(deftype T [a b]) ;加载一个同名的新类(投 T x) ;将旧实例转换为新类——失败;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>

I've been reading code and documentation to try to understand how class reloading works in clojure. According to many websites, such as http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html , whenever you load a class essentially you obtain the bytecode (via any data mechanism), convert the bytecode into an instance of class Class (via defineClass), and then resolve (link) the class via resolveClass. (Does defineClass implicitly call resolveClass?). Any given classloader is only allowed to link a class once. If it attempts to link an existing class, it does nothing. This creates a problem since you cannot link a newly instantiated class, therefore you have to create a new instance of a classloader everytime you reload a class.

Going back to clojure, I tried examining the paths to load classes.

In clojure, you can define new classes in multiple ways depending on what you want:

Anonymous Class: reify proxy

Named Class: deftype defrecord (which uses deftype under the hood) gen-class

Ultimately, those codes point to clojure/src/jvm/clojure/lang/DynamicClassLoader.java

where DynamicClassLoader/defineClass creates an instance with super's defineClass and then caches the instance. When you want to retrieve the class, clojure load with a call to forName which calls the classloader and DynamicClassLoader/findClass, which first looks in the cache before delegating to the super class (which is contrary to the way most normal classloaders work, where they delegate first, than try it themselves.) The important point of confusion is the following: forName is documented to link the class before it returns but this would imply you can not reload a class from the existing DynamicClassLoader and instead need to create a new DynamicClassLoader, however I don't see this in the code. I understand that proxy and reify define anonymous classes, so their names are different thus can be treated as if its a different class. However, for the named classes, this breaks down. In real clojure code, you can have references to the old version of the classes and references to the new version of the classes simultaneously, but attempts to create new class instances will be of the new version.

Please explain how clojure is able to reload classes without creating new instances of DynamicClassLoader, if I can understand the mechanism to reload classes, I would like to extend this reloading functionality to java's .class files I may create using javac.

Notes: This question refers to class RELOADING, not simply dynamic loading. Reloading means that I have already interned a class but want to intern a new updated version of that instance.

I want to reiterate, that its not clear how clojure is able to reload deftype defined classes. Calling deftype eventually leads to a call to clojure.lang.DynamicClassLoader/defineClass. Doing this again leads another call to defineClass, but doing this manually results in a Linkage Error. What is happening underneath here that allows clojure to do this with deftypes?

解决方案

Not all of these language features use the same technique.

proxy

The proxy macro generates a class name based exclusively on the class and list of interfaces being inherited. The implementation of each method in this class delegates to a Clojure fn stored in the object instance. This allows Clojure to use the very same proxy class every time the same list of interfaces is inherited, whether the body of the macro is the same or not. No actual class reloading takes place.

reify

For reify, the method bodies are compiled directly into the class, so the trick proxy uses won't work. Instead, a new class is generated when the form is compiled, so if you change the body of the form and reload it, you get a whole new class (with a new generated name). So again, no actual class reloading takes place.

gen-class

With gen-class you specify a name for the generated class, so neither of the techniques used for proxy or reify will work. A gen-class macro contains only a sort of spec for a class, but none of the method bodies. The generated class, somewhat like proxy, defers to Clojure functions for the method bodies. But because a name is tied to the spec, unlike proxy it would not work to change the body of a gen-class and reload it, so gen-class is only available when compiling ahead-of-time (AOT compilation) and no reloading is allowed without restarting the JVM.

deftype and defrecord

This is where real dynamic class reloading happens. I'm not deeply familiar with the internals of the JVM, but a little work with a debugger and the REPL makes one point clear: every time a class name needs to be resolved, such as when compiling code that uses the class or when the Class class's forName method is called, Clojure's DynamicClassLoader/findClass method is used. As you note this looks up the class name in in the DynamicClassLoader's cache, and this can be set to point to a new class by running deftype again.

Note the caveats in the tutorial you mentioned about the reloaded class being a different class, despite having the same name, still apply to Clojure classes:

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

Each top-level form in a Clojure program gets a fresh DynamicClassLoader which is used for any new classes defined within that form. This will include not only classes defined via deftype and defrecord but also reify and fn. This means that the classloader for x above is different than the new T. Note the numbers after the @s are different -- each gets its own classloader:

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

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

But as long as we don't define a new T class, new instances will have the same class with the same classloader. Note the number after the @ here is the same as the second one above:

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

这篇关于clojure 类重新加载如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆