How Tomcat destroys Java’s parental delegation model

Java’s Parental Delegation Model

Three-layer class loader for JDK8 and previous versions

  1. Bootstrap Class Loader
    Realized by C++ language, HotSpot is a part of the virtual machine itself. This class loader is responsible for loading files stored in the \lib directory, or in the path specified by the -Xbootclasspath parameter, and is recognized by the Java virtual machine. (identified by file name, such as rt.jar, tools.jar, class libraries with inconsistent names will not be loaded even if they are placed in the lib directory) class libraries Loaded into the virtual machine’s memory. When loaded through this class, null is returned when classloader is obtained in java code

  2. Extension Class Loader
    This class loader is implemented in the form of Java code in the class sun.misc.Launcher$ExtClassLoader. It is responsible for loading all class libraries in the \lib\ext directory, or in the path specified by the java.ext.dirs system variable.
    After JDK9, the modularization mechanism was introduced, and this class withdrew from the stage of history.

  3. Application Class Loader
    This class loader is implemented by sun.misc.Launcher$AppClassLoader. Since the application class loader is the return value of the getSystemClassLoader() method in the ClassLoader class, it is also called the “system class loader” in some occasions. It is responsible for loading all class libraries on the user class path (ClassPath), and developers can also use this class loader directly in the code. If the application has not customized its own class loader, generally this is the default class loader in the program.

  4. Custom class loader
    Users can declare a class loader in their java code, but the parent loader can only be “Application Class Loader”. This may cause ClassNotFoundException

Parents Delegation Model, the parent delegation model requires that in addition to the top-level startup class loader, all other class loaders should have their own parent class loaders. However, the parent-child relationship between class loaders here is generally not implemented through inheritance. Instead, the composition relationship is usually used to reuse the code of the parent loader.

ClassLoader class loading process

The specific class loading in the Java code is done through the loadClass method in the Class class

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {<!-- -->
        synchronized (getClassLoadingLock(name)) {<!-- -->
            // First, check if the class has already been loaded
// Find whether the class exists from the jvm cache, this place should be the cache inside the thread
            Class<?> c = findLoadedClass(name);
            if (c == null) {<!-- -->
                try {<!-- -->
                    if (parent != null) {<!-- -->
                        c = parent.loadClass(name, false);
                    } else {<!-- -->
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {<!-- -->
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                // Some irrelevant code
if (c == null) {<!-- -->
// This method is empty in the ClassLoder class and needs to be implemented by subclasses, that is
                    c = findClass(name);
                }
            }
            if (resolve) {<!-- -->
// jdk11 this method is useless.
                resolveClass(c);
            }
            return c;
        }
    }
  1. lookup from vm cache
  2. Search from the parent class loader, if the parent class loader is empty, then search from Bootstrap Class Loader
  3. Find from your own findClass overloaded method
  4. Throw an exception

The working process of the parent delegation model is: if a class loader receives a class loading request, it will not try to load the class itself first, but delegates the request to the parent class loader to complete. Classes at each level This is true for loaders, so all load requests should eventually be transmitted to the top-level startup class loader, and only when the parent loader feedbacks that it cannot complete the load request (the required class is not found in its search scope) , the subloader will try to complete the loading by itself.

Tomcat’s class loading model

Common Class Loader

The public class loader located at the top level of Tomcat architecture. The parent loader is Java’s Application Class Loader. By default, it reads common in the conf/catalina.properties configuration file. .loader attribute, the default value is: ${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home }/lib","${catalina.home}/lib/*.jar

Catalina Class Loader

With Common Class Loader as the parent loader, it is used to load the class loader of the tomcat application server. By default, the server in the conf/catalina.properties configuration file is read. .loader attribute, the default value is empty

Shared Class Loader

With Common Class Loader as the parent loader, the parent class loader of all web applications reads the shared.loader in the conf/catalina.properties configuration file by default attribute, the default value is empty

Webapp Class Loader

Use Shared Class Loader as the parent loader to load Class files and resources in the /WEB-INF/classes directory, /WEB-INF/libThe jar package file in the directory is only visible to the current web application

The class corresponding to this level of ClassLoader is org.apache.catalina.loader.WebappClassLoaderBase rewriting the loadClass method, which destroys the “parental delegation model< in java /strong>“

 public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {<!-- -->

        synchronized (JreCompat. isGraalAvailable() ? this : getClassLoadingLock(name)) {<!-- -->
            if (log.isDebugEnabled()) {<!-- -->
                log.debug("loadClass(" + name + ", " + resolve + ")");
            }
            Class<?> clazz = null;

            // Log access to stopped class loader
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            clazz = findLoadedClass0(name);
            if (clazz != null) {<!-- -->
                if (log.isDebugEnabled()) {<!-- -->
                    log.debug(" Returning class from cache");
                }
                if (resolve) {<!-- -->
                    resolveClass(clazz);
                }
                return clazz;
            }

            // (0.1) Check our previously loaded class cache
            clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
            if (clazz != null) {<!-- -->
                if (log.isDebugEnabled()) {<!-- -->
                    log.debug(" Returning class from cache");
                }
                if (resolve) {<!-- -->
                    resolveClass(clazz);
                }
                return clazz;
            }

            // (0.2) Try loading the class with the bootstrap class loader, to prevent
            // the webapp from overriding Java SE classes. This implements
            // SRV.10.7.2
            String resourceName = binaryNameToPath(name, false);

            // In fact, this place can only get ExtensionClassLoader in Java, because BootClassLoader is null
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {<!-- -->
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
                // details of how this may trigger a StackOverflowError
                // Given these reported errors, catch Throwable to ensure any
                // other edge cases are also caught
                URL url;
                if (securityManager != null) {<!-- -->
                    PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                    url = AccessController.doPrivileged(dp);
                } else {<!-- -->
                    url = javaseLoader.getResource(resourceName);
                }
                tryLoadingFromJavaseLoader = (url != null);
            } catch (Throwable t) {<!-- -->
                // Swallow all exceptions apart from those that must be re-thrown
                ExceptionUtils. handleThrowable(t);
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }

            if (tryLoadingFromJavaseLoader) {<!-- -->
                try {<!-- -->
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {<!-- -->
                        if (resolve) {<!-- -->
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {<!-- -->
                    //Ignore
                }
            }

            // (0.5) Permission to access this class when using a SecurityManager
            if (securityManager != null) {<!-- -->
                int i = name.lastIndexOf('.');
                if (i >= 0) {<!-- -->
                    try {<!-- -->
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {<!-- -->
                        String error = sm.getString("webappClassLoader.restrictedPackage", name);
                        log. info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }

            // Whether to use delegation mode,
            boolean delegateLoad = delegate || filter(name, true);

            // (1) Delegate to our parent if requested
            if (delegateLoad) {<!-- -->
                if (log.isDebugEnabled()) {<!-- -->
                    log.debug(" Delegating to parent classloader1 " + parent);
                }
                try {<!-- -->
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {<!-- -->
                        if (log.isDebugEnabled()) {<!-- -->
                            log.debug("Loading class from parent");
                        }
                        if (resolve) {<!-- -->
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {<!-- -->
                    //Ignore
                }
            }

            // (2) Search local repositories
            if (log.isDebugEnabled()) {<!-- -->
                log.debug("Searching local repositories");
            }
            try {<!-- -->
                clazz = findClass(name);
                if (clazz != null) {<!-- -->
                    if (log.isDebugEnabled()) {<!-- -->
                        log.debug("Loading class from local repository");
                    }
                    if (resolve) {<!-- -->
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {<!-- -->
                //Ignore
            }

            // (3) Delegate to parent unconditionally
            if (!delegateLoad) {<!-- -->
                if (log.isDebugEnabled()) {<!-- -->
                    log.debug(" Delegating to parent classloader at end: " + parent);
                }
                try {<!-- -->
                    clazz = Class. forName(name, false, parent);
                    if (clazz != null) {<!-- -->
                        if (log.isDebugEnabled()) {<!-- -->
                            log.debug("Loading class from parent");
                        }
                        if (resolve) {<!-- -->
                            resolveClass(clazz);
                        }
                        returnclazz;
                    }
                } catch (ClassNotFoundException e) {<!-- -->
                    //Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }

To sum up, there are several steps:
When breaking the delegate mode:

  1. Load from cache
  2. Loaded from the ExtensionClassLoader in the JVM, because this place is in the delegation mode, it will be searched according to the path of Bootstrap -> Extension
  3. load from the current class loader
  4. Loaded from the parent loader, this place will also follow the delegation mode, so it will be searched according to the Bootstrap -> Extension -> Application -> Common -> Shared path

When delegation mode is turned on:

  1. Load from cache
  2. Loaded from the ExtensionClassLoader in the JVM, because this place is in the delegation mode, it will be searched according to the path of Bootstrap -> Extension
  3. Loaded from the parent loader, this place will also follow the delegation mode, so it will be searched according to the Bootstrap -> Extension -> Application -> Common -> Shared path
  4. load from the current class loader
syntaxbug.com © 2021 All Rights Reserved.