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

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

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

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

Анонимный класс: reify proxy

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

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

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

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

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

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

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's 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, Обратите внимание на цифры после @s разные - у каждого свой загрузчик классов:

(.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>
Другие вопросы по тегам