ClassLoader of Tomcat source code research

In order to place them centrally, during the process of collecting data, all the information was directly put on CSDN in the form of drafts. After collecting data for a week, I deleted all the information this morning because of the unskilled operation of CSDN. . . . . . . . . . . .

1 Overview

Although I started trying to read the source code of Tomcat more than a year ago, the source code of Tomcat is still relatively large, so the understanding of Tomcat has been relatively fragmented and has not formed a system. Recently, I encountered some problems caused by ClassLoader, so I decided to take the opportunity to study the application of ClassLoader in Tomcat. In this study, we will explain it according to the time line.

2. ClassLoader initialization

2.1 BootstrapClass

BootstrapAs the entry point of Tomcat startup logic, there are two main parts of the code involved in ClassLoader, namely initand initClassLoadersmethods:

Bootstrap.initmethod

initClassLoaders(); // 重点关注这个
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);

Bootstrap.initClassLoadersMethod
The initialized URLClassLoader here follows the ClassLoader's level delegation reception mechanism .

// 该方法负责给类级字段`commonLoader`, `catalinaLoader`, `sharedLoader` 赋值。
private void initClassLoaders() {
    try {
        // 这里的common.loader,类型为URLClassLoader
        // 其parent classloader为 sun.misc.Launcher$AppClassLoader; 
        // 注意该ClassLoader是遵从ClassLoader的 上级委托接待机制 的
        // 就是这个loader负责加载Server和Context
        commonLoader = createClassLoader("common", null);
        if( commonLoader == null ) {
            // no config file, default to this loader - we might be in a 'single' env.
            commonLoader=this.getClass().getClassLoader();
        }
        // 默认情况下 catalinaLoader, sharedLoader等于上面的commonLoader 
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

Let's take a look at the environment variables of Tomcat runtime through JVISUALVM
Tomcat system parameters

  1. sun.misc.Launcher$AppClassLoaderResponsible for loading is the Class System.getProerty(“java.class.path”)under .
  2. The common.loader is responsible for loading the Class indicated by ${common.loader}.
  3. So loading Server, Context is still the URLClassLoader generated by common.loader.

2.2 Catalina.createStartDigesterMethods

  1. What this method does is build a conf/server.xmlDigester component that parses XML configuration files (by default).
  2. Assign values ​​to the relevant fields of the ClassLoader in the relevant container (Container):
    1. Catalina.SetParentClassLoaderRule. Responsible for Enginesetting parentClassLoader(the field is defined in the container base class ContainerBase).
    2. located HostRuleSetin CopyParentClassLoaderRule. Set ( Hostthe parentClassLoaderfield is defined in the container base class ContainerBase).
    3. The above as the source of the parentClassLoadervalue is the class that loads Catalina -- sun.misc.Launcher$AppClassLoader.
    4. For it overrides the method Contextinherited from .ContainerBasegetParentClassLoader
  3. Note that there is such a relevant code in this method digester.setUseContextClassLoader(true);. Combining the above, Thread.currentThread().setContextClassLoader(catalinaLoader);we can conclude that Classes such as Server and Context are loaded using the above catalinaLoader (URLClassLoader type, and the parent ClassLoader is the loaded Catalinaclass— sun.misc.Launcher$AppClassLoader).

3. StandardContextClass

  1. The focus is still on this.
  2. It overrides methods inherited from the base ContainerBaseclass getParentClassLoader.

    @Override
    public ClassLoader getParentClassLoader() {
        // 如果自身显式定义了, 则直接返回
        if (parentClassLoader != null)
            return (parentClassLoader);
        // 如果显式设置了privileged为true, 则返回加载自身的ClassLoader,也就是common.loader代表的URLClassLoader,注意该URLClassLoader的parent ClassLoader为sun.misc.Launcher$AppClassLoader。
        if (getPrivileged()) {
            return this.getClass().getClassLoader();
        } else if (parent != null) { // 如果parent不为null, 在StandardContext的情况下, 这是肯定满足的。所以这里的parent为Host,因此这里的返回值也是 sun.misc.Launcher$AppClassLoader
            return (parent.getParentClassLoader());
        }
        return (ClassLoader.getSystemClassLoader());
    }
  3. Which involves three related classes of Tomcat itself

    1. WebappLoader Loader
    2. WebappClassLoaderBase ClassLoader
    3. DefaultInstanceManager InstanceManager

The first thing we will look at is the startInternalmethod

//
//  代码实在太多了,故进行了非必要的删减
//

// ------------------------------------ 初始化Loader
if (getLoader() == null) {
    WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
    webappLoader.setDelegate(getDelegate());
    setLoader(webappLoader);
}

...

// Binding thread
// 将Loader关联的ClassLoader绑定到上下文
// 这里返回的应该是在Bootstrap.java的init()方法中绑定的catalinaLoader[默认加载路径在conf/catalina.properties的common.loader对应的值]
ClassLoader oldCCL = bindThread();

// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
    ((Lifecycle) loader).start();

// since the loader just started, the webapp classloader is now created.
// By calling unbindThread and bindThread in a row, we setup the current Thread CCL to be the webapp classloader
unbindThread(oldCCL); // 将common.loader的CCL绑定回当前线程
// setup the current Thread CCL to be the webapp classloader [说得已经比较清楚了,将Loader相关的CCL再次关联当前线程]
// 返回common.loader
oldCCL = bindThread();

...
// Unbinding thread
// 再次将common.loader的CCL绑定回当前线程
unbindThread(oldCCL);

// Binding thread
oldCCL = bindThread(); // 再再一次将Loader关联的ClassLoader绑定到上下文

// ------------------------------------ 初始化InstanceManager
if (getInstanceManager() == null) {
    javax.naming.Context context = null;
    if (isUseNaming() && getNamingContextListener() != null) {
        context = getNamingContextListener().getEnvContext();
    }
    Map<String, Map<String, String>> injectionMap = buildInjectionMap(
            getIgnoreAnnotations() ? new NamingResources(): getNamingResources());
    setInstanceManager(new DefaultInstanceManager(context,
            injectionMap, this, this.getClass().getClassLoader()));
    getServletContext().setAttribute(
            InstanceManager.class.getName(), getInstanceManager());
}

// 回调ServletContainerInitializer相关类(@HandlesTypes标注的实现类)
//使用StandardContext的InstanceManager字段来负责实例化了Servlet,Filter,Listener
// listenerStart() 
// filterStart() 
// loadOnStartup(findChildren()) 
// 相关的解释, 我们放到InstanceManager节点中
... 

// Unbinding thread
unbindThread(oldCCL); // 再再次将common.loader的CCL绑定回当前线程

...

// Close all JARs right away to avoid always opening a peak number of files on startup
if (getLoader() instanceof WebappLoader) {
    ((WebappLoader) getLoader()).closeJARs(true);
}

Now let's take a look at WebappClassLoaderBase, WebappLoader, InstanceManager in detail

4. WebappLoaderClass

  1. LoaderThe definition of the interface shows that it is not responsible for the actual class loading.
  2. LifecycleThe interface startInternalis implemented , and it is also instantiated and configuredWebappClassLoader in the method .
  3. LoaderThe main function of is to perform related configuration; the logic for loading Class is still WebappClassLoaderin .

5. WebappClassLoaderClass

  1. Most of the logic resides in its base class WebappClassLoaderBase.
  2. The main concern is to override the inheritance from the base class loadClass,findClass (you can read "In-depth Analysis of Java Web Technology (Revised Edition)" P157).
  3. loadClassThe amount of code in the findClassmethod is relatively large, and interested readers can study it by themselves. related to
    1. Customize various rules when loading classes.
    2. Cache previously loaded classes.
    3. Preload certain classes.

WebappClassLoaderBaseThere are some fields in the base class that are worth paying attention to
. 1. triggersThe Servlet specification requires that related classes are not allowed to be loaded by the servlet container. Here is the relevant implementation. And the sentence we often see in the console: validateJarFile(xxx) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: xxx, is located in its validateJarFilemethod - line 3373
2. packageTriggersBefore delegating to the parent classloader, it is not allowed to load itself first, a collection of classes.
3. wait

6. InstanceManagerClass

  1. The implementation class is DefaultInstanceManager.
  2. The assignment of several key fields classLoader,containerClassLoader

    // ------------------- DefaultInstanceManager类的构造函数
    // catalinaContext实际类型为: StandardContext  , 
    // 所以这个classLoader应该就是WebappClassLoader
    classLoader = catalinaContext.getLoader().getClassLoader();
    
    privileged = catalinaContext.getPrivileged();
    
    // containerClassLoader就是加载StandardContext的ClassLoader, 结合之前的digester.setUseContextClassLoader(true); 
    // 所以这个containerClassLoader应该就是common.loader(即URLClassLoader,其praent Classloader是sun.misc.Launcher$AppClassLoader)
    this.containerClassLoader = containerClassLoader;
  3. It is this InstanceManagerclass that is responsible for loading all , Servlet, Filter, Listeneretc.

    1. StandardContext.listenerStart(). Here you can find StandardContextthe InstanceManagerfields used to be responsible for instantiation Listener.
    2. StandardContext.filterStart(). Here we can finally trace ApplicationFilterConfigthe constructor of the class, which is also the used StandardContextfield InstanceManagerto be responsible for instantiation Filter.
    3. loadOnStartup(findChildren()). StandardWrapperThe method that can be traced here loadServlet, it is StandardContextthe InstanceManagerfield that is used to be responsible for instantiation Servlet.

The logic responsible for loading Servlet, Filter and Listener in this class is its internal loadClassmethod ,

// ----------------------- DefaultInstanceManager.loadClass 
/*
    大概逻辑是
        1. 使用containerClassLoader进行加载   (这个containerClassLoader应该就是common.loader(即URLClassLoader,其praent Classloader是sun.misc.Launcher$AppClassLoader))
            1. "org.apache.catalina" package内的Class.
            2. ContainerServlet的实现类
        2. 剩下的都使用classLoader来进行加载 (这个classLoader应该就是WebappClassLoader )
        3. 再结合之前的测试, 可以得出结论: 我们自定义的类都是由WebappClassLoader来加载的.
*/
protected Class<?> loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
    // 特定的 package 内
    if (className.startsWith("org.apache.catalina")) {
        return containerClassLoader.loadClass(className);
    }
    try {
        Class<?> clazz = containerClassLoader.loadClass(className);
        // 关于这个ContainerServlet, 本篇文章就不涉及了
        if (ContainerServlet.class.isAssignableFrom(clazz)) {
            return clazz;
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
    }
    return classLoader.loadClass(className);
}

7. Summary

  1. By default, Bootstrapthe class-level field in a class catalinaLoader, sharedLoader, commonLoaderis the same URLClassLoader, and the URLClassLoaderParent ClassLoader is exactly that sun.misc.Launcher$AppClassLoader.
  2. The ClassLoader of the component configured in loading Serveretc. is the URLClassLoader indicated by the field. That is, the attention set in Catalina's method is here to obey the parent's priority .config/server.xmlcatalinaLoadercreateStartDigesterdigester.setUseContextClassLoader(true);
  3. Host, the field of Engine parentClassLoader, its assignment is done through the digester component, and its value is the loaded CatalinaClassLoader, ie sun.misc.Launcher$AppClassLoader.
  4. The ClassLoader that loads Server and Context is common.loader (ie java.net.URLClassLoader), and its parent Classloader is sun.misc.Launcher$AppClassLoader(ie: the class that loads Catalina).
  5. Context uses InstanceManager to complete the instantiation work. Servlet, Filter, Listener use it to complete the instantiation operation, and also use it to load the related classes of Servlet, Filter, and Listener.

    1. The URLClassLoader referred to by common.loader loads the classes under the "org.apache.catalina" package, as well as the implementation class of ContainerServlet.
    2. The rest are loaded using classLoader (this classLoader should be WebappClassLoader). This is also the ClassLoader that we most often come into contact with in the development process. Our custom classes are loaded by the WebappClassLoader. Pay attention to the originWebappClassLoader of the parent ClassLoaderStandardContext.getParentClassLoader .
  6. The actual work of loading classes is finally handed over to WebappClassLoader, most of the work logic is completed by the base class WebappClassLoaderBase, and the Loader interface basically only does the peripheral configuration work.

  1. "In-depth Analysis of Java Web Technology Insider (Revised Edition)" P168
  2. 《How Tomcat Works》 P157
  3. Tomcat class loader and class isolation and sharing between applications

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324749612&siteId=291194637