Как работает перезагрузка класса clojure? - PullRequest
22 голосов
/ 19 сентября 2011

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

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

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

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

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

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

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

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

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

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

1 Ответ

31 голосов
/ 19 сентября 2011

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

прокси

Макрос 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 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>
...