Tomcat类加载机制 - 极悦
首页 课程 师资 教程 报名

Tomcat类加载机制

  • 2020-12-03 17:16:48
  • 1361次 极悦

说到Tomcat类加载机制,我们不得不提及JVM的类加载,然后Tomcat也是运行在JVM上的。所以,我们先抛砖引玉,一起来看看JVM类加载。

JVM类加载采用父类委托机制,当JVM运行过程中,用户需要加载某些类时,用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器树的顶层。最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类。如果一直到底层的类加载都没有加载到,那么就会抛出异常ClassNotFoundException。

回过头来,我们再来看Tomcat类加载,Tomcat类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。

下面的简图是Tomcat9版本的官方文档给出的Tomcat的类加载器的图。

    Bootstrap
          |
       System
          |
       Common
       /    \
  Webapp1   Webapp2 ..

Bootstrap :是Java的最高的加载器,用C语言实现,主要用来加载JVM启动时所需要的核心类,例如$JAVA_HOME/jre/lib/ext路径下的类。

System: 会加载CLASSPATH系统变量所定义路径的所有的类。

Common:会加载Tomcat路径下的lib文件下的所有类。

Webapp1、Webapp2……: 会加载webapp路径下项目中的所有的类。一个项目对应一个WebappClassLoader,这样就实现了应用之间类的隔离了。

这3个部分,在上面的Java双亲委派模型图中都有体现。不过可以看到ExtClassLoader没有画出来,可以理解为是跟bootstrap合并了,都是去JAVA_HOME/jre/lib下面加载类。那么Tomcat为什么要自定义类加载器呢?

隔离不同应用:部署在同一个Tomcat中的不同应用A和B,例如A用了Spring2.5。B用了Spring3.5,那么这两个应用如果使用的是同一个类加载器,那么Web应用就会因为jar包覆盖而无法启动。

灵活性:Web应用之间的类加载器相互独立,那么就可以根据修改不同的文件重建不同的类加载器替换原来的。从而不影响其他应用。

性能:如果在一个Tomcat部署多个应用,多个应用中都有相同的类库依赖。那么可以把这相同的类库让Common类加载器进行加载。

Tomcat自定义了WebAppClassLoader类加载器。打破了双亲委派的机制,即如果收到类加载的请求,会尝试自己去加载,如果找不到再交给父加载器去加载,目的就是为了优先加载Web应用自己定义的类。我们知道ClassLoader默认的loadClass方法是以双亲委派的模型进行加载类的,那么Tomcat既然要打破这个规则,就要重写loadClass方法,我们可以看WebAppClassLoader类中重写的loadClass方法。

@Override
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class clazz = null;
// 1. 从本地缓存中查找是否加载过此类
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

// 2. 从AppClassLoader中查找是否加载过此类
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        String resourceName = binaryNameToPath(name, false);
// 3. 尝试用ExtClassLoader 类加载器加载类,防止Web应用覆盖JRE的核心类
        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {
            URL url;
            if (securityManager != null) {
                PrivilegedActiondp = new PrivilegedJavaseGetResource(resourceName);
                url = AccessController.doPrivileged(dp);
            } else {
                url = javaseLoader.getResource(resourceName);
            }
            tryLoadingFromJavaseLoader = (url != null);
        } catch (Throwable t) {
            tryLoadingFromJavaseLoader = true;
        }

        boolean delegateLoad = delegate || filter(name, true);
 // 4. 判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类
        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
            }
        }
  // 5. 默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载
        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
        }

 // 6. 如果此时在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载
        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);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }
    throw new ClassNotFoundException(name);
}

我们总结起来就是Web应用默认的类加载顺序是(打破了双亲委派规则):

1.先从JVM的BootStrapClassLoader中加载。

2.加载Web应用下/WEB-INF/classes中的类。

3.加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。

4.加载上面定义的System路径下面的类。

5.加载上面定义的Common路径下面的类。

到此为止,Tomcat类加载机制逐渐明朗,Tomcat类加载机制的重点就是打破了双亲委派机制,WebAppClassLoader加载类的时候,绕开 AppClassLoader,直接先使用 ExtClassLoader 来加载类。这不仅保证了基础类不会被同时加载,也

保证了在同一个 Tomcat 下不同 web 之间的 class 是相互隔离的。好了。Tomcat的类加载机制就讲到这里,感兴趣的小伙伴可以去观看本站的Tomcat服务器教程,深入学习Tomcat里的各种技术。

选你想看

你适合学Java吗?4大专业测评方法

代码逻辑 吸收能力 技术学习能力 综合素质

先测评确定适合在学习

在线申请免费测试名额
价值1998元实验班免费学
姓名
手机
提交