variable significa que para esta donde crear configurar como archivos java jar classpath classloader

java - significa - ¿Cómo hace un cargador de clases para cargar las clases en el classpath de manifiesto?



java classpath que es (2)

Usé maven para construir un jar con una adición externa de classpath usando addClasspath .

Cuando ejecuto ese jar usando java -jar artifact.jar , puedo cargar clases de ese jar principal y de todos los jars en el directorio libs.

Sin embargo, si le pregunto a la propiedad del sistema java.class.path , solo mostrará el jar principal. Si le pido al cargador de clases del sistema sus URL ( ClassLoader.getSystemClassLoader().getURLs() ), solo devolverá el jar principal. Si le pregunto a cualquier clase contenida en alguna biblioteca por su cargador de clases, devolverá el cargador de clases del sistema.

¿Cómo es capaz el cargador de clases del sistema de cargar esas clases?

Tiene que tener algún conocimiento sobre esas bibliotecas para poder cargar clases de esas. ¿Hay alguna manera de pedirle este tipo de classpath "extendido"?


La respuesta corta es que la implementación es parte del funcionamiento interno de Sun y no está disponible a través de medios públicos. getURLs() solo devolverá las URL que se pasen. Hay una respuesta más larga, pero solo es atrevida.

Pasar por Oracle JVM 8 con el depurador me ha llevado a través de una estructura prácticamente idéntica a OpenJDK6 y aquí se puede ver dónde carga la ruta de clase.

Básicamente, el cargador de clases mantiene una pila de URL que aún no ha analizado en la memoria. Cuando se le pida que cargue una clase, extraerá las URL de la pila, las cargará como archivos de clase o archivos jar, y si son archivos jar, leerá las entradas del manifiesto y de la clase push en la pila. Cada vez que procesa un archivo, agrega el "cargador" que cargó ese archivo en el mapa del cargador (si no es así, para asegurarse de que no procesa el mismo archivo varias veces).

Puede acceder a este mapa si está realmente motivado (no lo recomendaría) con:

Field secretField = URLClassLoader.class.getDeclaredField("ucp"); secretField.setAccessible(true); Object ucp = secretField.get(loader); secretField = ucp.getClass().getDeclaredField("lmap"); secretField.setAccessible(true); return secretField.get(ucp);

Ejecuto eso en una configuración ficticia donde tengo dummy-plugin.jar que hace referencia a external.jar (en el manifiesto de dummy-plugin.jar) obtengo lo siguiente:

1) Inmediatamente después de crear el cargador de clases (antes de cargar cualquier clase):

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] getSecretUrlsStack=[file:.../dummy-plugin.jar] getSecretLmapField={}

2) Después de cargar una clase desde dummy-plugin.jar:

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] getSecretUrlsStack=[file:.../external.jar] getSecretLmapField={file:.../dummy-plugin.jar=sun.misc.URLClassPath$JarLoader@736e9adb}

3) Después de cargar una clase desde external.jar:

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] getSecretUrlsStack=[] getSecretLmapField={file:.../dummy-plugin.jar=sun.misc.URLClassPath$JarLoader@736e9adb, file:.../external.jar=sun.misc.URLClassPath$JarLoader@2d8e6db6}

Por extraño que parezca, esto parece ir en contra del JDK para URLClassLoader :

Las clases que se cargan tienen permiso otorgado de forma predeterminada solo para acceder a las URL especificadas cuando se creó el URLClassLoader.


El uso de la reflexión para acceder a un campo privado en la instancia del cargador de clases del sistema presenta varios problemas:

  • La accesión puede estar prohibida por un gerente de seguridad
  • La solución depende de la implementación

Otra solución menos "intrusiva" es:

  1. Para un cargador de clases determinado, enumere todo el manifiesto disponible cl.getResources("META-INF/MANIFEST.MF") . Estos manifiestos pueden ser de jar administrados por el cargador de clases actual o sus cargadores de clases ascendentes.
  2. Haz lo mismo con su cargador de clases padre
  3. Devuelve el conjunto de jarras de esos manifiestos en (1) pero no en (2)

El único requisito para que este método funcione es que jar en el classpath debe tener un manifiesto para poder ser devuelto (no hay mucho que preguntar).

/** * Returns the search path of URLs for loading classes and resources for the * specified class loader, including those referenced in the * {@code Class-path} header of the manifest of a executable jar, in the * case of class loader being the system class loader. * <p> * Note: These last jars are not returned by * {@link java.net.URLClassLoader#getURLs()}. * </p> * @param cl * @return */ public static URL[] getURLs(URLClassLoader cl) { if (cl.getParent() == null || !(cl.getParent() instanceof URLClassLoader)) { return cl.getURLs(); } Set<URL> urlSet = new LinkedHashSet(); URL[] urLs = cl.getURLs(); URL[] urlsFromManifest = getJarUrlsFromManifests(cl); URLClassLoader parentCl = (URLClassLoader) cl.getParent(); URL[] ancestorUrls = getJarUrlsFromManifests(parentCl); for (int i = 0; i < urlsFromManifest.length; i++) { urlSet.add(urlsFromManifest[i]); } for (int i = 0; i < ancestorUrls.length; i++) { urlSet.remove(ancestorUrls[i]); } for (int i = 0; i < urLs.length; i++) { urlSet.add(urLs[i]); } return urlSet.toArray(new URL[urlSet.size()]); } /** * Returns the URLs of those jar managed by this classloader (or its * ascendant classloaders) that have a manifest * @param cl * @return */ private static URL[] getJarUrlsFromManifests(ClassLoader cl) { try { Set<URL> urlSet = new LinkedHashSet(); Enumeration<URL> manifestUrls = cl.getResources("META-INF/MANIFEST.MF"); while (manifestUrls.hasMoreElements()) { try { URL manifestUrl = manifestUrls.nextElement(); if(manifestUrl.getProtocol().equals("jar")) { urlSet.add(new URL(manifestUrl.getFile().substring(0, manifestUrl.getFile().lastIndexOf("!")))); } } catch (MalformedURLException ex) { throw new AssertionError(); } } return urlSet.toArray(new URL[urlSet.size()]); } catch (IOException ex) { throw new RuntimeException(ex); } }