Подтвердить что ты не робот

Как перезагрузка класса clojure работает?

Я читал код и документацию, чтобы понять, как перезагрузка класса работает в clojure. Согласно многим веб-сайтам, таким как http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html, всякий раз, когда вы загружаете класс, вы получаете байт-код (через любой механизм данных), конвертируете байт-код в экземпляр класса Class (через defineClass), а затем разрешить (связать) класс с помощью resolveClass. (Определяет ли defineClass неявно вызов resolveClass?). Каждому данному загрузчику классов можно только связать один класс. Если он пытается связать существующий класс, он ничего не делает. Это создает проблему, поскольку вы не можете связать недавно созданный класс, поэтому вам нужно создать новый экземпляр загрузчика классов при каждом перезагрузке класса.

Возвращаясь к clojure, я попытался изучить пути для загрузки классов.

В clojure вы можете определять новые классы несколькими способами в зависимости от того, что вы хотите:

Анонимный класс: материализовать прокси

Именованный класс: deftype defrecord (который использует deftype под капотом) Gen-класс

В конечном итоге эти коды указывают на clojure/src/jvm/clojure/lang/DynamicClassLoader.java

где DynamicClassLoader/defineClass создает экземпляр с супер defineClass и затем кэширует экземпляр. Когда вы хотите получить класс, clojure загрузите вызов toName, который вызывает загрузчик классов и DynamicClassLoader/findClass, который сначала смотрит в кеш, прежде чем делегировать суперкласс (что противоречит способу работы большинства нормальных загрузчиков классов, где они делегируют в первую очередь, чем пытаться сами.) Важным моментом путаницы является следующее: forName документируется, чтобы связать класс до его возвращения, но это означало бы, что вы не можете перезагрузить класс из существующего DynamicClassLoader и вместо этого для создания нового DynamicClassLoader, но я не вижу этого в коде. Я понимаю, что прокси и reify определяют анонимные классы, поэтому их имена различны, поэтому их можно рассматривать так, как если бы это был другой класс. Однако для названных классов это разрушается. В реальном коде clojure вы можете иметь ссылки на старую версию классов и ссылки на новую версию классов одновременно, но попытки создания новых экземпляров класса будут иметь новую версию.

Пожалуйста, объясните, как clojure может перезагружать классы без создания новых экземпляров DynamicClassLoader, если я могу понять механизм перезагрузки классов, я хотел бы расширить эту функциональность перезагрузки до java.class файлов, которые я могу создать с помощью javac.

Примечания: Этот вопрос относится к классу RELOADING, а не просто к динамической загрузке. Перезагрузка означает, что я уже интернировал класс, но хочу ставить новую обновленную версию этого экземпляра.

Я хочу повторить, что неясно, как clojure может перезагрузить определенные классы. Вызов deftype в конечном итоге приводит к вызову clojure.lang.DynamicClassLoader/defineClass. Выполнение этого снова приводит к другому вызову defineClass, но при этом вручную возникает ошибка Linkage. Что происходит здесь, что позволяет clojure делать это с помощью ловких лиц?

4b9b3361

Ответ 1

Не все эти языковые функции используют ту же технику.

прокси

Макрос proxy генерирует имя класса, основанное исключительно на классе и списке унаследованных интерфейсов. Реализация каждого метода в этом классе делегируется Clojure fn, хранящемуся в экземпляре объекта. Это позволяет Clojure использовать тот же самый прокси-класс каждый раз, когда наследуется один и тот же список интерфейсов, независимо от того, является ли тело макроса одинаковым или нет. Фактическая перезагрузка класса не происходит.

материализовать

В reify тела метода скомпилируются непосредственно в класс, поэтому использование трюка proxy не будет работать. Вместо этого создается новый класс при компиляции формы, поэтому, если вы измените тело формы и перезагрузите его, вы получите целый новый класс (с новым сгенерированным именем). Поэтому снова не происходит реальной перезагрузки класса.

генераторного класса

С 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 [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, который используется для любых новых классов, определенных в этой форме. Это будет включать не только классы, определенные через deftype и defrecord, но также reify и fn. Это означает, что загрузчик классов для x выше отличается от нового T. Обратите внимание, что числа после @ различны - каждый получает свой собственный загрузчик классов:

(.getClassLoader (class x))
;=> #<DynamicClassLoader [email protected]>

(.getClassLoader (class (T. 3 4)))
;=> #<DynamicClassLoader [email protected]>

Но пока мы не определяем новый класс T, новые экземпляры будут иметь один и тот же класс с одним и тем же загрузчиком классов. Обратите внимание, что число после @ здесь совпадает с номером, указанным выше:

(.getClassLoader (class (T. 4 5)))
;=> #<DynamicClassLoader [email protected]>