Diferencia entre el cargador de clases de contexto del hilo y el cargador de clases normal

242

¿Cuál es la diferencia entre el cargador de clases de contexto de un hilo y un cargador de clases normal?

Es decir, si Thread.currentThread().getContextClassLoader()y getClass().getClassLoader()devuelve diferentes objetos del cargador de clases, ¿cuál se usará?

abracadabra
fuente

Respuestas:

151

Cada clase usará su propio cargador de clases para cargar otras clases. Así que si ClassA.classlas referencias ClassB.classa continuación, ClassBtiene que estar en la ruta de clase del cargador de clases deClassA , o de sus padres.

El cargador de clases de contexto de hilo es el cargador de clases actual para el hilo actual. Se puede crear un objeto a partir de una clase ClassLoaderCy luego pasarlo a un hilo propiedad de ClassLoaderD. En este caso, el objeto debe usarse Thread.currentThread().getContextClassLoader()directamente si desea cargar recursos que no están disponibles en su propio cargador de clases.

David Roussel
fuente
1
¿Por qué dice que ClassBdebe estar en el classpath del ClassAcargador de (o ClassAlos padres del cargador)? ¿No es posible que ClassAel cargador se anule loadClass(), de modo que se pueda cargar con éxito ClassBincluso cuando ClassBno está en su classpath?
Pacerier
8
De hecho, no todos los cargadores de clases tienen un classpath. Cuando escribí "ClassB necesita estar en el classpath del cargador de clases de ClassA", quise decir "ClassB necesita que el cargador de clases de ClassA pueda cargarlo". El 90% de las veces significan lo mismo. Pero si no está utilizando un cargador de clases basado en URL, solo el segundo caso es verdadero.
David Roussel
¿Qué significa decir que " ClassA.classreferencias ClassB.class"?
jameshfisher
1
Cuando ClassA tiene una declaración de importación para ClassB, o si hay un método en ClassA que tiene una variable local de tipo ClassB. Eso activará la carga de ClassB, si aún no está cargada.
David Roussel
Creo que mi problema está relacionado con este tema. ¿Qué opinas de mi solución? Sé que no es un buen patrón, pero no tengo otra idea de cómo solucionarlo: stackoverflow.com/questions/29238493/…
Marcin Erbel
109

Esto no responde a la pregunta original, pero como la pregunta está altamente clasificada y vinculada a cualquier ContextClassLoaderconsulta, creo que es importante responder la pregunta relacionada de cuándo se debe usar el cargador de clases de contexto. Respuesta corta: ¡ nunca use el cargador de clases de contexto ! Pero configúralo engetClass().getClassLoader() cuando tenga que llamar a un método al que le falta unClassLoader parámetro.

Cuando el código de una clase pide cargar otra clase, el cargador de clases correcto para usar es el mismo cargador de clases que la clase que llama (es decir, getClass().getClassLoader()). Así es como funcionan las cosas el 99.9% del tiempo porque esto es lo que hace la JVM la primera vez que construye una instancia de una nueva clase, invoca un método estático o accede a un campo estático.

Cuando desee crear una clase utilizando la reflexión (como al deserializar o cargar una clase con nombre configurable), la biblioteca que hace la reflexión siempre debe preguntarle a la aplicación qué cargador de clase usar, al recibir el ClassLoaderparámetro como parte de la aplicación. La aplicación (que conoce todas las clases que necesitan construirse) debería pasarlagetClass().getClassLoader() .

Cualquier otra forma de obtener un cargador de clases es incorrecta. Si una biblioteca usa hacks como Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader()o sun.reflect.Reflection.getCallerClass()es un error causado por una deficiencia en la API. Básicamente, Thread.getContextClassLoader()existe solo porque quien diseñó la ObjectInputStreamAPI olvidó aceptar elClassLoader como parámetro, y este error ha perseguido a la comunidad Java hasta el día de hoy.

Dicho esto, muchas clases de JDK usan uno de los pocos hacks para adivinar algún cargador de clases para usar. Algunos usan el ContextClassLoader(que falla cuando ejecuta diferentes aplicaciones en un grupo de subprocesos compartido, o cuando abandona el ContextClassLoader null), algunos recorren la pila (que falla cuando el llamante directo de la clase es una biblioteca), algunos usan el cargador de clases del sistema (lo cual está bien, siempre que esté documentado para usar solo clases en elCLASSPATH ) o cargador de clases bootstrap, y algunos usan una combinación impredecible de las técnicas anteriores (lo que solo hace las cosas más confusas). Esto ha resultado en mucho llanto y crujir de dientes.

Cuando utilice una API de este tipo, primero, trate de encontrar una sobrecarga del método que acepte el cargador de clases como parámetro . Si no hay un método razonable, intente configurar ContextClassLoaderantes de la llamada API (y restablecerlo después):

ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
    // call some API that uses reflection without taking ClassLoader param
} finally {
    Thread.currentThread().setContextClassLoader(originalClassLoader);
}
yonran
fuente
55
Sí, esta es la respuesta que señalaría a cualquiera que haga la pregunta.
Marko Topolnik
66
Esta respuesta se enfoca en usar el cargador de clases para cargar clases (para instanciarlas a través de la reflexión o similar), mientras que otro propósito para el que se usa (y, de hecho, el único propósito para el que lo he usado personalmente) es cargar recursos. ¿Se aplica el mismo principio, o hay situaciones en las que desea obtener recursos a través del cargador de clases de contexto en lugar del cargador de clases de llamador?
Egor Hans
90

Hay un artículo en javaworld.com que explica la diferencia => Qué ClassLoader debe usar

(1)

Los cargadores de clases de contexto de subprocesos proporcionan una puerta trasera alrededor del esquema de delegación de carga de clases.

Tome JNDI por ejemplo: sus agallas se implementan mediante clases de bootstrap en rt.jar (comenzando con J2SE 1.3), pero estas clases centrales de JNDI pueden cargar proveedores JNDI implementados por proveedores independientes y potencialmente implementados en la ruta de clase de la aplicación. Este escenario requiere un cargador de clases padre (el primordial en este caso) para cargar una clase visible para uno de sus cargadores de clase hijos (el sistema, por ejemplo). La delegación normal de J2SE no funciona, y la solución consiste en hacer que las clases centrales de JNDI usen cargadores de contexto de subprocesos, por lo tanto, "tunelizan" efectivamente a través de la jerarquía del cargador de clases en la dirección opuesta a la delegación adecuada.

(2) de la misma fuente:

Esta confusión probablemente permanecerá con Java por algún tiempo. Tome cualquier API J2SE con carga dinámica de recursos de cualquier tipo e intente adivinar qué estrategia de carga usa. Aquí hay una muestra:

  • JNDI usa cargadores de clases de contexto
  • Class.getResource () y Class.forName () usan el cargador de clases actual
  • JAXP utiliza cargadores de clases de contexto (a partir de J2SE 1.4)
  • java.util.ResourceBundle usa el cargador de clases actual de la persona que llama
  • Los controladores de protocolo URL especificados a través de la propiedad del sistema java.protocol.handler.pkgs se buscan solo en los cargadores de clases de arranque y del sistema.
  • La API de serialización de Java utiliza el cargador de clases actual de la persona que llama de forma predeterminada
Timour
fuente
Como se sugiere que la solución es hacer que las clases centrales de JNDI usen cargadores de contexto de subprocesos, no entendí cómo esto ayuda en este caso. Queremos cargar las clases de proveedores de implementación usando el cargador de clases padre pero no son visibles para el cargador de clases padre . Entonces, ¿cómo podemos cargarlos usando parent, incluso si configuramos este cargador de clases padre en el cargador de clases de contexto del hilo?
Sunny Gupta
66
@SAM, la solución sugerida es en realidad todo lo contrario de lo que dices al final. No se bootstrapestá configurando el cargador de clases padre como el cargador de clases de contexto, sino el systemcargador de clases classpath hijo con el que Threadse está configurando. Las JNDIclases se aseguran de usar Thread.currentThread().getContextClassLoader()para cargar las clases de implementación JNDI disponibles en el classpath.
Ravi Thapliyal
"La delegación normal de J2SE no funciona", ¿puedo saber por qué no funciona? ¿Porque Bootstrap ClassLoader solo puede cargar la clase desde rt.jar y no puede cargar la clase desde -classpath de la aplicación? ¿Correcto?
YuFeng Shen
37

Agregando a la respuesta @David Roussel, las clases pueden ser cargadas por múltiples cargadores de clases.

Vamos a entender cómo funciona el cargador de clases .

Del blog de javin paul en javarevisited:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ClassLoader Sigue tres principios.

Principio de delegación

Una clase se carga en Java, cuando es necesario. Supongamos que tiene una clase específica de aplicación llamada Abc.class, la primera solicitud de carga de esta clase llegará a Application ClassLoader, que delegará en su Extension ClassLoader principal, que delegará aún más en el cargador de clases Primordial o Bootstrap

  • Bootstrap ClassLoader es responsable de cargar archivos de clase JDK estándar desde rt.jar y es el padre de todos los cargadores de clases en Java. El cargador de clases Bootstrap no tiene padres.

  • Extension ClassLoader delega la solicitud de carga de clase a su principal, Bootstrap y, si no tiene éxito, carga el formulario jre / lib / ext o cualquier otro directorio señalado por la propiedad del sistema java.ext.dirs

  • El cargador de clases de sistema o aplicación es responsable de cargar las clases específicas de la aplicación desde la variable de entorno CLASSPATH, la opción de línea de comando -classpath o -cp, el atributo Class-Path del archivo de manifiesto dentro de JAR.

  • Application class loader es un elemento secundario de Extension ClassLoader y está implementado por sun.misc.Launcher$AppClassLoaderclase.

NOTA: Excepto el cargador de clases Bootstrap , que se implementa en lenguaje nativo principalmente en C, todos los cargadores de clases Java se implementan usando java.lang.ClassLoader.

Principio de visibilidad

De acuerdo con el principio de visibilidad, Child ClassLoader puede ver la clase cargada por Parent ClassLoader pero viceversa no es cierto.

Principio de singularidad

De acuerdo con este principio, Child ClassLoader no debe volver a cargar una clase cargada por Parent.

Ravindra babu
fuente