Let’s look at the example of URLClassloader

As you can see from the example, you can load the class bytecode of the resource in the URL of the class into the JVM as Java by creating the URLClassloader and passing in the collection of urls, and then calling loadCLass directly to pass in the fully qualified name of the class resource. Lang. Class object.

URLClassloader class inheritance relationship

URLClassloader inherits from SecureClassloader and Classloader.

The important field of URLClassloader is ucP, which holds the path of classes and resources

/* The search path for classes and resources */
private final URLClassPath ucp;
Copy the code

The constructor of URLClassLoader passes the loading path of the resource,

  1. Initializes the parent class’s constructor.
  2. Create the URLClassPath object and pass in the urls of the resource and the AccessContext AccessContext.
public URLClassLoader(URL[] urls) {
    super();
    this.acc = AccessController.getContext();
    this.ucp = new URLClassPath(urls, acc);
}
Copy the code

URLClassloader overrides the parent class’s findClass

URLClassloader inherits Classloader by overriding the findClass method of its parent class. AccessController#doPrivileged passes in an anonymous inner class, using the template class to check for access permissions before and after the run function is executed

  1. Replaces all ‘.’ in the path name string of the passed class with ‘/’, ending with ‘.class’.
  2. Use the getResource method of the UCP (variable of the URLClassPath object created in the constructor)
  3. If the query Resouce is not empty, the Class resource is loaded and converted to a Class object by calling defineClass.
protected Class<? > findClass(final String name)throws ClassNotFoundException{ final Class<? > result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<? >>() { String path = name.replace('.', '/').concat(".class"); Resource res = ucp.getResource(path, false); if (res ! = null) { try { return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }Copy the code

GetResource implementation of URLClassPath

First, let’s look at the important properties of URLClassPath

/* The original search path of URLs. */
private final ArrayList<URL> path;

/* The deque of unopened URLs */
private final ArrayDeque<URL> unopenedUrls;

/* The resulting search path of Loaders */
private final ArrayList<Loader> loaders = new ArrayList<>();

/* Map of each URL opened to its corresponding Loader */
private final HashMap<String, Loader> lmap = new HashMap<>();
Copy the code

If the URLStreamHandlerFactory argument is passed in, it defaults to null, and the main construct is a set of urls (Path and unopenedUrls), jarHandler is empty, and loaders defaults to an empty set.

public URLClassPath(URL[] urls, AccessControlContext acc) { this(urls, null, acc); } public URLClassPath(URL[] urls, URLStreamHandlerFactory factory, @SuppressWarnings("removal") AccessControlContext acc) { ArrayList<URL> path = new ArrayList<>(urls.length); ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(urls.length); for (URL url : urls) { path.add(url); unopenedUrls.add(url); } this.path = path; this.unopenedUrls = unopenedUrls; if (factory ! = null) { jarHandler = factory.createURLStreamHandler("jar"); } else { jarHandler = null; } if (DISABLE_ACC_CHECKING) this.acc = null; else this.acc = acc; }Copy the code

URLClassPath# getResource logic

  1. Iterates through all loaders and calls the getResource method to look for the resource with the class’s full name passed in, returning null if found, and null if not.
public Resource getResource(String name, boolean check) { Loader loader; for (int i = 0; (loader = getLoader(i)) ! = null; i++) { Resource res = loader.getResource(name, check); if (res ! = null) { return res; } } return null; }Copy the code

URLClassPath#getLoader(int index) main logic

  1. While loop, the condition is that the loader set size is smaller than index + 1. Since index starts from 0, the condition is 0 < 1 and the condition is true.
  2. The logic of the loop body is as follows: 2.1 Lock unopenedUrls and pop up a URL object from the head of unopenedUrls array. Call URLUtil#urlNoFragString to convert the URL to a string for storing the HashMap key. If the lmap(Map

    ) object contains a String converted from the URL, it is loaded. Execute continue to end the loop. 2.3 Call the getLoader overload function to pass in the URL resource path and convert the URL into Loader 2.4 Obtain the Loader classpath set, If it is not empty, call the push function to add the URL to the head of the double-ended array of unopenedUrls.
    ,loader>
  3. After the loop body ends, the loader at the index position in the Loader collection is fetched.
private synchronized Loader getLoader(int index) { if (closed) { return null; } while (loaders.size() < index + 1) { final URL url; synchronized (unopenedUrls) { url = unopenedUrls.pollFirst(); if (url == null) return null; } String urlNoFragString = URLUtil.urlNoFragString(url); if (lmap.containsKey(urlNoFragString)) { continue; } Loader loader; try { loader = getLoader(url); URL[] urls = loader.getClassPath(); if (urls ! = null) { push(urls); } } catch (IOException e) { // Silently ignore for now... continue; } catch (SecurityException se) { if (DEBUG) { System.err.println("Failed to access " + url + ", " + se ); } continue; } // Finally, add the Loader to the search path. loaders.add(loader); lmap.put(urlNoFragString, loader); } return loaders.get(index); }Copy the code

The main logic of URLClassPath#getLoader is to convert the URL resource path into a Loader object. Direct getLoader PrivilegedExceptionAction run function in logic.

  1. Obtain the ULR protocol and resource file name.
  2. Check that the file name is not empty and the resource file name ends with a ‘/’, if true, then proceed to check whether the URL protocol is ‘file’ or ‘jar’ or whatever. Create the Fileloader.2.2 protocol as ‘jar’ and the default URL as URLStreamHandler. Create JarLoader. 2.3 If the protocol is other, create Loader.
  3. If false is judged in 2, JarLoader is created.
private Loader getLoader(final URL url) throws IOException { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<>() { public Loader run() throws IOException { String protocol = url.getProtocol(); String file = url.getFile(); if (file ! = null && file.endsWith("/")) { if ("file".equals(protocol)) { return new FileLoader(url); } else if ("jar".equals(protocol) && isDefaultJarHandler(url) && file.endsWith("! /")) { // extract the nested URL URL nestedUrl = new URL(file.substring(0, file.length() - 2)); return new JarLoader(nestedUrl, jarHandler, lmap, acc); } else { return new Loader(url); } } else { return new JarLoader(url, jarHandler, lmap, acc); } } }, acc); } catch (PrivilegedActionException pae) { throw (IOException)pae.getException(); }}Copy the code

Then, we mainly take JarLoader as an example to divide,

JarLoader getResource query resource implementation

First look at the main fields of JarLoader, mainly JarFile,

private static class JarLoader extends Loader { private JarFile jar; private final URL csu; private JarIndex index; private URLStreamHandler handler; private final HashMap<String, Loader> lmap; / / omit}Copy the code
  1. The first call to ensureOpen ensures that the JAR file has been parsed.
  2. The JarEntry object is obtained by passing in the class name by calling getJarEntry of JarFile.
  3. If JarEntry is not empty, call checkResource to return the corresponding resource.

4. If JarIndex is null, null is returned. 5. If jarentry is empty but JarIndex is not, getResource is called to query the class resource.

Resource getResource(final String name, boolean check) { try { ensureOpen(); } catch (IOException e) { throw new InternalError(e); } final JarEntry entry = jar.getJarEntry(name); if (entry ! = null) return checkResource(name, check, entry); if (index == null) return null; HashSet<String> visited = new HashSet<>(); return getResource(name, check, visited); }Copy the code
JarLoader#ensureOpen
  1. The incoming URL is first converted into a JarFile object using the getJarFile function.
  2. If JarIndex in JarFile is not empty, the JarFiles set of JarIndex is traversed, and it is spliced into a URL to add lmap set.
private void ensureOpen() throws IOException {
    if (jar == null) {
        try {
                        jar = getJarFile(csu);
                        index = JarIndex.getJarIndex(jar);
                        if (index != null) {
                         String[] jarfiles = index.getJarFiles();
                         for (int i = 0; i < jarfiles.length; i++) {
                           try {
                      URL jarURL = new URL(csu, jarfiles[i]); 
            String urlNoFragString = URLUtil.urlNoFragString(jarURL);
          if (!lmap.containsKey(urlNoFragString)) {
                           lmap.put(urlNoFragString, null);
                        }
                     } catch (MalformedURLException e) {
                         //省略
        } catch (IOException e) {
            throw e.getException();
        }
    }
}
Copy the code
JarLoader#getJarFile
  1. If the protocol is ‘file’, pass the URL directly by following optimization logic 1.1 to create a FileURLMapper object. Check whether the File on the Window platform exists JarFile as described in 1.2 Creating File files packing URL paths.
  2. Otherwise, call openConnect of URL to obtain URLConnection, and then obtain jarfile file of URLConnection. This step is to remotely download jarfile file of brass drum.
private JarFile getJarFile(URL url) throws IOException { // Optimize case where url refers to a local jar file if (isOptimizable(url)) { FileURLMapper p = new FileURLMapper(url); if (! p.exists()) { throw new FileNotFoundException(p.getPath()); } return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ, JarFile.runtimeVersion())); } URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection(); uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION); JarFile jarFile = ((JarURLConnection)uc).getJarFile(); return checkJar(jarFile); }Copy the code
JarFile analytical

First look at the important Field of JarFile where JarEntry is the jar file entry and Runtime.Version is the Runtime Version.

public class JarFile extends ZipFile {
    private static final Runtime.Version BASE_VERSION;
    private static final int BASE_VERSION_FEATURE;
    private static final Runtime.Version RUNTIME_VERSION;
    private static final boolean MULTI_RELEASE_ENABLED;
    private static final boolean MULTI_RELEASE_FORCED;
    private static final ThreadLocal<Boolean> isInitializing = new ThreadLocal<>();

    private SoftReference<Manifest> manRef;
    private JarEntry manEntry;
    private JarVerifier jv;
    private boolean jvInitialized;
    private boolean verify;
    private final Runtime.Version version;  // current version
    private final int versionFeature;       // version.feature()
Copy the code

JarFile class is inherited ZipFile, it essentially classes and resources compressed file JarFile Specification reference docs.oracle.com/javase/7/do…

The JarFile constructor \

  1. Initialize the parent ZipFile constructor to initialize file and mode. (mode: zipfile.open_read)
  2. Assign the verify field. (The value passed in above is true)
  3. Assign the versionFeature field.
public JarFile(File file, boolean verify, int mode, Runtime.Version version) throws IOException { super(file, mode); this.verify = verify; Objects.requireNonNull(version); // omit this.versionfeature = this.version.feature(); }Copy the code

ZipFile constructor

  1. Assign name to the path of the passed File
  2. Assignment creates a CleanableResource object to assign to the RES field
public ZipFile(File file, int mode, Charset charset) throws IOException
{                                            
    String name = file.getPath();
    file = new File(name);
    this.name = name;
    this.res = new CleanableResource(this, ZipCoder.get(charset), file, mode);
}
Copy the code

The CleanableResource constructor \

  1. Use the CleanerFactory to create a Cleaner registered ZipFile object and CleanableResource itself,
  2. Create an empty Set object, in this case a WeakHashSet collection, to avoid memory leaks if the file stream is not referenced and not recycled.
  3. Create an inflaterCache array.
  4. Create the Source object.
CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode) throws IOException { this.cleanable = CleanerFactory.cleaner().register(zf, this); this.istreams = Collections.newSetFromMap(new WeakHashMap<>()); this.inflaterCache = new ArrayDeque<>(); this.zsrc = Source.get(file, (mode & OPEN_DELETE) ! = 0, zc); }Copy the code

Here the Cleaner object mainly uses the virtual reference mechanism to clean the istreams compressed file collection, inflaterCache file collection, compressed file Souce. Finally, the Souce class contains Jar file format parsing. For details, refer to the jarFil format.

In summary, this article mainly implements Class loading by unloading URLClassloader, which is mainly to inherit the Classloader to rewrite its findClass method, and realize the SEARCH for URL resources, load local files or remote Class resources files into the JVM, and parse into java.lang.class objects.