On how Tomcat breaks the mechanism of parental entrustment
- 2021-11-02 03:33:54
- OfStack
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
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.