How does the loadload class load classes into the manifest class path?

I used maven to create a flag with the addition of external additions to classes using addClasspath .

When I run this jar using java -jar artifact.jar , it can load classes from this main jar and from all jars in the libs directory.

However, if I ask for the java.class.path system property, it will list only the main jar. If I ask for the system class loader for my URLs ( ClassLoader.getSystemClassLoader().getURLs() ), it will also return the main jar. If I ask for any class contained in some library for my class loader, it will return a system class loader.

How can a system class loader load these classes?

He must have some knowledge of these libraries in order to load classes from them. Is there any way to ask him about this "extended" class path?

+7
java classpath jar classloader
source share
2 answers

The short answer is that implementation is part of Sun’s internal work and is not available through public funds. getURLs() only ever return the URLs that were passed. There is a longer answer, but this is only for courage.

Going through the Oracle JVM 8 with the debugger led me to an almost identical structure like OpenJDK6, and you can see where it loads the class path here .

Basically, the class loader stores a stack of URLs that it has not yet parsed in memory. When asked to load a class, it pops the URLs from the stack, loads them in the form of class files or jar files, and if they are jar files, it will read the manifest and move the class path entries onto the stack. Each time it processes a file, it adds a β€œloader” that downloads the file to the loader card (if nothing else, to ensure that it does not process the same file several times).

You can access this map if you are really motivated (not recommended):

  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); 

By running this on a dummy setup, where I have dummy-plugin.jar that references external.jar (in the dummy-plugin.jar manifest), I get the following:

1) Immediately after creating the class loader (before loading any class):

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

2) After loading the class from dummy-plugin.jar:

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

3) After loading the class from 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 } 

Oddly enough, it seems like it flies in front of the JDK for URLClassLoader :

By default, loadable classes only get permission to access the URLs specified when creating the URLClassLoader.

+5
source share

Using reflection to access a private field in an instance of a system class loader presents several problems:

  • Enabling may be prohibited by the security manager
  • The decision is implementation dependent.

Another solution, less "intrusive", is:

  • For this classloader, list all available manifest cl.getResources("META-INF/MANIFEST.MF") . These manifests can be from jars managed by the current class loader or its descendant class loaders.
  • Do the same for your parent class loader
  • Returns the set of cans of these manifest in (1) but not in (2)

The only requirement for this method is that banks on the way to classes must have a manifest to return (not much to ask).

 /** * 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); } } 
+2
source share

All Articles