clojure classloader

¿Cómo funciona la recarga de la clase clojure?



classloader (1)

He estado leyendo el código y la documentación para tratar de entender cómo funciona la recarga de clases en clojure. Según muchos sitios web, como http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html , cada vez que carga una clase, obtiene el código de bytes (a través de cualquier mecanismo de datos), convierte el bytecode en una instancia de clase Clase (a través de defineClass), y luego resuelva (enlace) la clase a través de resolverClass. (¿DefineClass implícitamente llama resolverClass?). Cualquier cargador de clases dado solo puede vincular una clase una vez. Si intenta vincular una clase existente, no hace nada. Esto crea un problema ya que no puede vincular una clase con una instancia reciente, por lo tanto, tiene que crear una nueva instancia de un cargador de clases cada vez que vuelva a cargar una clase.

Volviendo a clojure, traté de examinar los caminos para cargar clases.

En clojure, puedes definir nuevas clases de varias maneras dependiendo de lo que quieras:

Clase anónima: reify proxy

Clase con nombre: deftype defrecord (que usa deftype debajo del capó) gen-class

En última instancia, esos códigos apuntan a clojure / src / jvm / clojure / lang / DynamicClassLoader.java

donde DynamicClassLoader / defineClass crea una instancia con super''s defineClass y luego almacena en caché la instancia. Cuando desee recuperar la clase, cierre la carga con una llamada a forName que llama al cargador de clases y a DynamicClassLoader / findClass, que primero busca en el caché antes de delegar a la súper clase (lo cual es contrario a la forma en que funciona la mayoría de los cargadores de clases normales, donde delegue primero, luego inténtelo él mismo.) El punto importante de confusión es el siguiente: se documenta forName para vincular la clase antes de que regrese, pero esto implicaría que no puede volver a cargar una clase desde el DynamicClassLoader existente y en su lugar debe crear un nuevo DynamicClassLoader Sin embargo, no veo esto en el código. Entiendo que proxy y reify definen clases anónimas, por lo que sus nombres son diferentes, por lo que se pueden tratar como si fueran una clase diferente. Sin embargo, para las clases nombradas, esto se descompone. En el código de clojure real, puede tener referencias a la versión anterior de las clases y referencias a la nueva versión de las clases simultáneamente, pero los intentos de crear nuevas instancias de clase serán de la nueva versión.

Explique cómo clojure puede recargar clases sin crear nuevas instancias de DynamicClassLoader. Si puedo entender el mecanismo para recargar clases, me gustaría extender esta funcionalidad de recarga a los archivos .class de java que pueda crear usando javac.

Notas: Esta pregunta se refiere a la clase RELOADING, no simplemente a la carga dinámica. Recargar significa que ya he internado una clase pero quiero internar una nueva versión actualizada de esa instancia.

Quiero reiterar, que no está claro cómo clojure es capaz de recargar clases definidas de Deftype. Al llamar a deftype, eventualmente lleva a una llamada a clojure.lang.DynamicClassLoader / defineClass. Volver a hacer esto lleva a otra llamada a defineClass, pero al hacerlo manualmente se produce un error de vinculación. ¿Qué está sucediendo aquí debajo que permita a los curiosos hacer esto con los tipos?


No todas estas características del lenguaje utilizan la misma técnica.

apoderado

La macro proxy genera un nombre de clase basado exclusivamente en la clase y la lista de interfaces que se heredan. La implementación de cada método en esta clase se delega a un Clojure fn almacenado en la instancia del objeto. Esto permite a Clojure utilizar la misma clase de proxy cada vez que se hereda la misma lista de interfaces, independientemente de si el cuerpo de la macro es el mismo o no. No tiene lugar la recarga real de la clase.

cosificar

Para la reify , los cuerpos de los métodos se compilan directamente en la clase, por lo que los usos de proxy truco no funcionarán. En su lugar, se genera una nueva clase cuando se compila el formulario, por lo que si cambia el cuerpo del formulario y lo vuelve a cargar, obtendrá una clase completamente nueva (con un nuevo nombre generado). Así que, una vez más, no tiene lugar la recarga de clase real.

clase gen

Con gen-class usted especifica un nombre para la clase generada, por lo que ninguna de las técnicas utilizadas para proxy o reify funcionará. Una macro gen-class contiene solo una especie de especificación para una clase, pero ninguno de los cuerpos del método. La clase generada, un tanto como proxy , difiere a las funciones de Clojure para los cuerpos del método. Pero como el nombre está vinculado a la especificación, a diferencia del proxy , no funcionaría cambiar el cuerpo de una gen-class y recargarlo, por lo que gen-class solo está disponible cuando se compila con anticipación (compilación AOT) y no se vuelve a cargar Se permite sin reiniciar la JVM.

deftype y defrecord

Aquí es donde ocurre la recarga de clase dinámica real. No estoy muy familiarizado con los aspectos internos de la JVM, pero un poco de trabajo con un depurador y el REPL aclara un punto: cada vez que se debe resolver un nombre de clase, como cuando se compila el código que usa la clase o cuando Se forName método forName la clase, se forName método DynamicClassLoader/findClass Clojure. Como nota, esto busca el nombre de la clase en el caché de DynamicClassLoader, y esto se puede configurar para que apunte a una nueva clase ejecutando deftype nuevamente.

Tenga en cuenta que las advertencias en el tutorial que mencionó acerca de que la clase recargada es una clase diferente, a pesar de tener el mismo nombre, todavía se aplican a las clases de 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)

Cada formulario de nivel superior en un programa de Clojure obtiene un DynamicClassLoader nuevo que se usa para cualquier clase nueva definida en ese formulario. Esto incluirá no solo las clases definidas a través de deftype y defrecord sino también reify y fn . Esto significa que el cargador de clases para x anterior es diferente al nuevo T Tenga en cuenta que los números después de @ s son diferentes: cada uno tiene su propio cargador de clases:

(.getClassLoader (class x)) ;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@337b4703> (.getClassLoader (class (T. 3 4))) ;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@451c0d60>

Pero mientras no definamos una nueva clase T , las nuevas instancias tendrán la misma clase con el mismo cargador de clases. Note que el número después de @ aquí es el mismo que el segundo arriba:

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