简体   繁体   English

clojure类重装如何工作?

[英]How does clojure class reloading work?

I've been reading code and documentation to try to understand how class reloading works in clojure. 我一直在阅读代码和文档,试图了解类重新加载在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. 根据许多网站,如http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html ,每当你加载一个类本质上你获得字节码(通过任何数据机制),转换将字节码转换为类Class的实例(通过defineClass),然后通过resolveClass解析(链接)该类。 (Does defineClass implicitly call resolveClass?). (defineClass是否隐式调用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. 回到clojure,我尝试检查加载类的路径。

In clojure, you can define new classes in multiple ways depending on what you want: 在clojure中,您可以根据需要以多种方式定义新类:

Anonymous Class: reify proxy 匿名类:reify代理

Named Class: deftype defrecord (which uses deftype under the hood) gen-class 命名类:deftype defrecord(在引擎盖下使用deftype)gen-class

Ultimately, those codes point to clojure/src/jvm/clojure/lang/DynamicClassLoader.java 最终,这些代码指向clojure / src / jvm / clojure / lang / DynamicClassLoader.java

where DynamicClassLoader/defineClass creates an instance with super's defineClass and then caches the instance. 其中DynamicClassLoader / defineClass使用super的defineClass创建一个实例,然后缓存该实例。 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. 当你想要检索类时,clojure加载调用forName调用类加载器和DynamicClassLoader / findClass,它首先在委托给超类之前查看缓存(这与大多数普通类加载器的工作方式相反,它们在哪里首先委托,而不是自己尝试。) 混淆的重点如下:forName被记录为在返回之前链接类,但这意味着你不能从现有的DynamicClassLoader重新加载一个类,而是需要创建一个新的DynamicClassLoader但是我没有在代码中看到这一点。 I understand that proxy and reify define anonymous classes, so their names are different thus can be treated as if its a different class. 我理解代理和reify定义了匿名类,因此它们的名称不同,因此可以将其视为不同的类。 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. 在真正的clojure代码中,您可以同时引用旧版本的类和对新版本类的引用,但尝试创建新的类实例将是新版本。

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

Notes: This question refers to class RELOADING, not simply dynamic loading. 注意:此问题涉及类RELOADING,而不仅仅是动态加载。 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. 我想重申一下,不清楚clojure如何能够重新加载deftype定义的类。 Calling deftype eventually leads to a call to clojure.lang.DynamicClassLoader/defineClass. 调用deftype最终会调用clojure.lang.DynamicClassLoader / defineClass。 Doing this again leads another call to defineClass, but doing this manually results in a Linkage Error. 再次执行此操作会导致再次调用defineClass,但手动执行此操作会导致链接错误。 What is happening underneath here that allows clojure to do this with deftypes? 在这里发生了什么,允许clojure使用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. proxy宏仅根据要继承的接口和类列表生成类名。 The implementation of each method in this class delegates to a Clojure fn stored in the object instance. 此类中每个方法的实现委托给存储在对象实例中的Clojure fn。 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. 这允许Clojure在每次继承相同的接口列表时使用相同的代理类,无论宏的主体是否相同。 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. 对于reify ,方法体直接编译到类中,因此特技proxy使用不起作用。 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. 使用gen-class您可以为生成的类指定名称,因此用于proxyreify的技术都不起作用。 A gen-class macro contains only a sort of spec for a class, but none of the method bodies. gen-class宏只包含gen-class的一种规范,但不包含任何方法体。 The generated class, somewhat like proxy , defers to Clojure functions for the method bodies. 生成的类有点像proxy ,遵循方法体的Clojure函数。 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. 但是因为名称与规范相关联,与proxy不同,它不能改变gen-class的主体并重新加载它,所以gen-class只有在提前编译(AOT编译)并且没有重新加载时才可用允许而不重新启动JVM。

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

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: 请注意您提到的关于重新加载的类是另一个类的教程中的警告,尽管名称相同,仍然适用于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)

Each top-level form in a Clojure program gets a fresh DynamicClassLoader which is used for any new classes defined within that form. Clojure程序中的每个顶级表单都会获得一个新的DynamicClassLoader,用于该表单中定义的任何新类。 This will include not only classes defined via deftype and defrecord but also reify and fn . 这不仅包括通过deftypedefrecord定义的类,还包括reifyfn This means that the classloader for x above is different than the new T . 这意味着上面的x的类加载器与新的T不同。 Note the numbers after the @ s are different -- each gets its own classloader: 注意@ s之后的数字是不同的 - 每个都有自己的类加载器:

(.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. 但只要我们不定义新的T类,新实例将具有相同的类,具有相同的类加载器。 Note the number after the @ here is the same as the second one above: 注意@ here之后的数字与上面的第二个相同:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM