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

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

问题描述

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



回到clojure,



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



匿名类:
reify
代理



命名类:
deftype
defrecord
gen-class



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



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



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



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



我想重申,它不清楚clojure如何能够reload deftype定义的类。调用deftype最终导致调用clojure.lang.DynamicClassLoader / defineClass。这样做再次导致对defineClass的另一个调用,但手动导致链接错误。

解决方案

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



代理



代理宏会生成一个类名称仅基于继承的接口的类和列表。这个类中的每个方法的实现都委托给存储在对象实例中的Clojure fn。这允许Clojure在每次继承相同的接口列表时使用非常相同的代理类,无论宏的主体是否相同。

$ c>,方法体被直接编译成类,所以 proxy 使用的技巧不会工作。相反,在编译表单时会生成一个新类,因此如果更改表单的主体并重新加载,则会得到一个新的类(使用新生成的名称)。



gen-class



使用 gen-class 您为生成的类指定一个名称,因此 proxy reify 将工作。一个 gen-class 宏只包含一个类的规范,但没有一个方法体。生成的类,有点像 proxy ,推迟到方法体的Clojure函数。但是因为一个名字是绑定到规范,不同于 proxy 它不会工作改变一个 gen-class 并重新加载它,因此 gen-class 仅在提前编译(AOT编译)时可用,并且不允许重新加载而不重新启动JVM。



deftype和defrecord



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



请注意,你提到的关于重新加载的类是一个不同的类,尽管有相同的名称,仍然适用于Clojure类:

 (deftype T [ab]);定义一个名为T 
(def x(T. 1 2))的原始类;创建原始类的实例
(deftype T [a b]);加载一个同名的新类
(cast T x);将旧实例转换为新类 - 失败
; ClassCastException java.lang.Class.cast(Class.java:2990)

Clojure程序获得一个新的DynamicClassLoader,它用于在该表单中定义的任何新类。这不仅包括通过 deftype defrecord 定义的类,还包括 reify fn 。这意味着上面的 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天全站免登陆