On how Tomcat breaks the mechanism of parental entrustment

  • 2021-11-02 03:33:54
  • OfStack

Class loader for directory JVM
Class Loader for Tomcat
findClass
loadClass

We often encounter ClassNotFound exceptions, indicating that JVM failed while trying to load a class.

To solve this exception, you have to know

What is class loading JVM How to Load Classes Why did ClassNotFound appear

Think about how Tomcat loads and manages Servlet under Web application.
Tomcat loads and manages Web applications through the Context component, so today I will analyze the class loading mechanism of Tomcat in detail. But before that, it is necessary for us to preview the class loading mechanism of JVM under 1. I will answer the questions thrown at the beginning of 1 under 1, and then talk about how the class loader of Tomcat breaks the parental delegate mechanism of Java.

Class Loader for JVM

Class loading of Java is to load the bytecode format. class file into the method area of JVM, and establish an java. lang. Class object instance in JVM heap to encapsulate the data and methods related to Java class.

What is an Class object?
A template that can be understood as a business class, from which JVM creates specific business class object instances.

JVM does not load all. class files once at startup, but it is loaded only when the program uses this class during running.
JVM class loading is done by the class loader, and JDK provides an abstract class ClassLoader:


public abstract class ClassLoader {

    //  Each class loader has a parent loader 
    private final ClassLoader parent;
    
    public Class<?> loadClass(String name) {
  
        //  Find out if the class has been loaded 
        Class<?> c = findLoadedClass(name);
        
        //  If not loaded 
        if( c == null ){
          //  "Recursive" delegates to the parent loader to load 
          if (parent != null) {
              c = parent.loadClass(name);
          } else {
              //  If the parent loader is empty, find Bootstrap Is the loader loaded 
              c = findBootstrapClassOrNull(name);
          }
        }
        //  If the parent loader does not load successfully, call its own findClass To load 
        if (c == null) {
            c = findClass(name);
        }
        
        return c ; 
    }
    
    protected Class<?> findClass(String name){
       // 1.  Based on the class name passed in name To look for class files in a specific directory, put .class File read into memory 
          ...
          
       // 2.  Call defineClass Converts a byte array to Class Object 
       return defineClass(buf, off, len) ; 
    }
    
    //  Parses a bytecode array into 1 A Class Object, using the native Method implementation 
    protected final Class<?> defineClass(byte[] b, int off, int len){
       ...
    }
}

JVM's class loaders have a hierarchical parent-child relationship, with each class loader holding one parent field pointing to the parent loader.

defineClass tool method: Call native method to parse the bytecode of Java class into an Class object findClass is to find the. class file, which may come from the file system or network. After finding it, read the. class file to the memory to get the bytecode array, and then call the defineClass method to get the Class object

loadClass first checks whether the class has been loaded, if it has been loaded, it will return directly, otherwise it will be loaded by the parent loader.
This is a recursive call, that is, the child loader holds the parent loader reference. When a class loader needs to load an Java class, it will first delegate the parent loader to load it, and then the parent loader will search for the Java class in its own loading path. When the parent loader cannot find it in its own loading range, it will be returned to the child loader for loading. This is the parent delegate mechanism.

The working principle of JDK's class loader is 1, the difference is only that the loading path is different, that is, the path found by findClass is different.
The parental delegation mechanism is to ensure the uniqueness of an Java class in JVM. If you write a class with the same name as the core class of JRE, such as Object, the parental delegation mechanism ensures that the Object class in JRE is loaded, not the Object you wrote.
Because when AppClassLoader loads your Object class, it will delegate it to ExtClassLoader, and ExtClassLoader will delegate it to BootstrapClassLoader. BootstrapClassLoader finds that it has loaded Object class and will return directly instead of loading your Object class.

The parent-child relationship of the class loader is not achieved by inheritance, for example, AppClassLoader is not a subclass of ExtClassLoader, but parent of AppClassLoader points to ExtClassLoader object.
Therefore, if you customize the class loader, instead of inheriting AppClassLoader, you can inherit ClassLoader abstract class and rewrite findClass and loadClass.
Tomcat implements its own class loading through a custom class loader.
If you want to break parental delegation, you just need to rewrite loadClass, because the default implementation of loadClass is parental delegation mechanism.

Class Loader for Tomcat

Tomcat's custom class loader WebAppClassLoader breaks the parental delegate mechanism:
First, try to load a class by yourself, and then delegate it to the parent class loader if you can't find it. The purpose is to load the class defined by Web application first.
Just override the two methods of ClassLoader:

findClass


public Class<?> findClass(String name) throws ClassNotFoundException {
    ...
    
    Class<?> clazz = null;
    try {
            //1.  First in Web Find classes under the application directory  
            clazz = findClassInternal(name);
    }  catch (RuntimeException e) {
           throw e;
       }
    
    if (clazz == null) {
    try {
            //2.  If it is not found in the local directory, give it to the parent loader to find it 
            clazz = super.findClass(name);
    }  catch (RuntimeException e) {
           throw e;
       }
    
    //3.  If the parent class is not found, throw ClassNotFoundException
    if (clazz == null) {
        throw new ClassNotFoundException(name);
     }

    return clazz;
}

Workflow

Find the class to load in the Web application local directory first If it is not found, give it to the parent loader to find it, that is, AppClassLoader If the parent loader does not find this class, throw ClassNotFound

loadClass


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

    synchronized (getClassLoadingLock(name)) {
 
        Class<?> clazz = null;

        //1.  Local first cache Find out if the class has been loaded 
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        //2.  Object of the system class loader cache Find whether it was loaded in the 
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        // 3.  Try to use ExtClassLoader Class loader class loads, why? 
        ClassLoader javaseLoader = getJavaseClassLoader();
        try {
            clazz = javaseLoader.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 4.  Try to search in the local directory class And load 
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 5.  Try to use the system class loader ( That is AppClassLoader) To load 
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
       }
    
    //6.  All the above procedures failed to load and threw an exception 
    throw new ClassNotFoundException(name);
}

Workflow

First, find out if the class has been loaded in the local Cache That is, whether the class loader of Tomcat has already loaded this class. If the Tomcat class loader has not loaded the class, see if the system class loader has If none, let ExtClassLoader load, in order to prevent Web from overwriting the core class of JRE with its own class Because Tomcat needs to break the parental delegation, If a class called Object is defined in the Web application, if the Object class is loaded first, it will overwrite the Object class of JRE, so the Tomcat class loader will try to load it with ExtClassLoader first, because ExtClassLoader will delegate to BootstrapClassLoader to load it, and BootstrapClassLoader will find that it has loaded Object class and return it directly to Tomcat class loader, so that Tomcat class loader will not load Web application. If ExtClassLoader fails to load, that is, JRE does not have such a class, find and load it in the local Web application directory If there is no such class in the local directory, it means that it is not the class defined by Web application, so it is loaded by the system class loader. Please note here that the Web application is delivered to the system class loader through the Class. forName call, because the default loader for Class. forName is the system class loader. If all the above loading procedures fail, throw ClassNotFound

It can be seen that the Tomcat class loader breaks the parent delegate, and delegates it directly to the parent loader without 1, but loads it in the local directory first.
However, to avoid overwriting the JRE core class with the local directory class, the ExtClassLoader load is attempted first.
Then why not load it with AppClassLoader first?
If so, it becomes a parental delegate again, which is the secret of Tomcat class loader.


Related articles: