Java, Classpath, Classloading => Varias versiones del mismo jar / proyecto

117

Sé que esta puede ser una pregunta tonta para los programadores experimentados. Pero tengo una biblioteca (un cliente http) que requieren algunos de los otros frameworks / jars utilizados en mi proyecto. Pero todos requieren diferentes versiones principales como:

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

¿Es el cargador de clases lo suficientemente inteligente como para separarlos de alguna manera? ¿Probablemente no? ¿Cómo maneja esto el cargador de clases, en caso de que una clase sea la misma en los tres frascos? ¿Cuál está cargado y por qué?

¿El cargador de clases solo recoge exactamente un frasco o mezcla clases arbitrariamente? Entonces, por ejemplo, si una clase se carga desde la Versión-1.jar, ¿todas las demás clases cargadas desde el mismo cargador de clases irán todas al mismo jar?

¿Cómo maneja este problema?

¿Existe algún truco para "incorporar" de alguna manera los frascos en el "required.jar" para que sean vistos como "una unidad / paquete" por el Classloader, o de alguna manera vinculados?

jens
fuente

Respuestas:

56

Los problemas relacionados con el cargador de clases son un asunto bastante complejo. En cualquier caso, debe tener en cuenta algunos hechos:

  • Los cargadores de clases en una aplicación suelen ser más de uno. El cargador de clases bootstrap delega en el apropiado. Cuando crea una instancia de una nueva clase, se invoca el cargador de clases más específico. Si no encuentra una referencia a la clase que está intentando cargar, delega a su padre, y así sucesivamente, hasta que llegue al cargador de clases de arranque. Si ninguno de ellos encuentra una referencia a la clase que está intentando cargar, obtendrá una ClassNotFoundException.

  • Si tiene dos clases con el mismo nombre binario, que pueden buscarse con el mismo cargador de clases y desea saber cuál de ellas está cargando, solo puede inspeccionar la forma en que el cargador de clases específico intenta resolver un nombre de clase.

  • De acuerdo con la especificación del lenguaje Java, no hay una restricción de unicidad para un nombre binario de clase, pero por lo que puedo ver, debería ser único para cada cargador de clases.

Puedo encontrar una manera de cargar dos clases con el mismo nombre binario, e implica tenerlas cargadas (y todas sus dependencias) por dos cargadores de clases diferentes que anulan el comportamiento predeterminado. Un ejemplo aproximado:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

Siempre encontré la personalización del cargador de clases una tarea complicada. Prefiero sugerir evitar múltiples dependencias incompatibles si es posible.

Luca Putzu
fuente
13
El cargador de clases bootstrap delega en el apropiado. Cuando crea una instancia de una nueva clase, se invoca el cargador de clases más específico. Si no encuentra una referencia a la clase que está intentando cargar, la delega a su padre. Por favor, tengan paciencia conmigo, pero depende de la política del cargador de clases, que es por defecto Parent First. En otras palabras, la clase secundaria primero le pedirá a su padre que cargue la clase y se cargará solo si toda la jerarquía no pudo cargarla, ¿no?
deckingraj
5
No, normalmente un cargador de clases delega a su padre antes de buscar la clase en sí. Vea la clase javadoc para Classloader.
Joe Kearney
1
Creo que Tomcat lo hace de la manera que se describe aquí, pero la delegación "convencional" es preguntarle al padre primero
rogerdpack
@deckingraj: después de buscar en Google, encontré esto en los documentos de Oracle: "En el diseño de delegación, un cargador de clases delega la carga de clases a su padre antes de intentar cargar una clase por sí mismo. [...] Si el cargador de clases padre no puede cargar una clase, el cargador de clases intenta cargar la clase en sí. En efecto, un cargador de clases es responsable de cargar solo las clases que no están disponibles para el padre ". Investigaré más. Si esto emergerá como la implementación predeterminada, actualizaré la respuesta en consecuencia. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Luca Putzu
20

Cada carga de clase elige exactamente una clase. Por lo general, el primero que se encuentra.

OSGi tiene como objetivo resolver el problema de múltiples versiones del mismo jar. Equinox y Apache Felix son las implementaciones comunes de código abierto para OSGi.

Tarlog
fuente
6

Classloader cargará las clases del jar que estaba en la ruta de clases primero. Normalmente, las versiones incompatibles de la biblioteca tendrán diferencias en los paquetes, pero en un caso poco probable, son realmente incompatibles y no se pueden reemplazar con una: pruebe con jarjar.

Alex Gitelman
fuente
6

Los cargadores de clases cargan la clase a pedido. Esto significa que la clase requerida primero por su aplicación y las bibliotecas relacionadas se cargaría antes que otras clases; la solicitud para cargar las clases dependientes se emite normalmente durante el proceso de carga y vinculación de una clase dependiente.

Es probable que encuentre LinkageErrormensajes que indiquen que se han encontrado definiciones de clases duplicadas para los cargadores de clases, por lo general, no intentan determinar qué clase debe cargarse primero (si hay dos o más clases del mismo nombre presentes en la ruta de clases del cargador). A veces, el cargador de clases cargará la primera clase que ocurre en la ruta de clases e ignorará las clases duplicadas, pero esto depende de la implementación del cargador.

La práctica recomendada para resolver este tipo de errores es utilizar un cargador de clases independiente para cada conjunto de bibliotecas que tengan dependencias en conflicto. De esa manera, si un cargador de clases intenta cargar clases desde una biblioteca, las clases dependientes serían cargadas por el mismo cargador de clases que no tiene acceso a las otras bibliotecas y dependencias.

Vineet Reynolds
fuente
1

Puede usar URLClassLoaderfor require para cargar las clases desde una versión diff-2 de jar:

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();
Pankaj Kalra
fuente