在学习Tomcat中的类加载器,并且Tomcat为什么要实现自己的类加载器打破双亲委派模型原因之前,我们首先需要知道Java中定义的类加载器是什么,双亲委派模型是什么。
类加载器负责在程序运行时将java文件动态加载到JVM中
从Java虚拟机的角度来讲的话,存在两种不同的类加载器:
其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader
,其中其他类加载器大概又分为
ExtClassLoader
实现,它负责加载 JAVA_HOME/lib/ext
目录中的所有类,或者被 java.ext.dir
系统变量所指定的路径中所有的类。 AppClassLoader
实现的,它负责加载用户类路径(ClassPath)上所指定的所有类,如果应用中没有自定义自己的类加载器,那么一般情况就是程序中默认的类加载器。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性
上图中展示的层次结构,称之为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其他加载器都应该有自己的父加载器。这里的父子关系不是通过继承来实现的,而是通过设置 parent
变量来实现的。
双亲委派模型工作过程是:如果收到一个类加载的请求,本身不会先加载此类,而是会先将此请求委派给父类加载器去完成,每个层次都是如此,直到启动类加载器中,只有父类都没有加载此文件,那么子类才会尝试自己去加载。
为什么要设置双亲委派模型呢?其实是为了保证Java程序的稳定运行,例如Object类,它是存放在 rt.jar
中,无论哪一个类加载器要加载Object类,最终都会委托给顶层的BootStrapClassLoader,所以所有的类中使用的Object都是同一个类,相反如果没有双亲委派模型的话,那么随意一个类加载器都可以定义一个新的Object类,那么应用程序将会变得非常混乱。其实双亲委派模型代码非常简单。实现在ClassLoader中的loadClass方法下。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,检查请求类是否被加载过 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 如果没被本类类加载器加载过,先委托给父类进行加载 if (parent != null) { c = parent.loadClass(name, false); } else { // 如果没有父类,则表明在顶层,就交给BootStrap类加载器加载 c = findBootstrapClassOrNull(name); } // 如果最顶层的类也找不到,那么就会抛出ClassNotFoundException异常 } catch (ClassNotFoundException e) { } // 如果父类都没有加载过此类,子类才开始加载此类 if (c == null) { c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } } protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
我们可以看到 findClass
方法是需要子类自己去实现的逻辑。
下面的简图是Tomcat9版本的官方文档给出的Tomcat的类加载器的图。
Bootstrap | System | Common / / Webapp1 Webapp2 ..
$JAVA_HOME/jre/lib/ext CLASSPATH
这3个部分,在上面的Java双亲委派模型图中都有体现。不过可以看到ExtClassLoader没有画出来,可以理解为是跟bootstrap合并了,都是去 JAVA_HOME/jre/lib
下面加载类。 那么Tomcat为什么要自定义类加载器呢?
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) { PrivilegedAction<URL> dp = 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); }
最后借用Tomcat官网上的话总结:
Web应用默认的类加载顺序是(打破了双亲委派规则):
/WEB-INF/classes /WEB-INF/lib/*.jap
如果在配置文件中配置了 <Loader delegate="true"/>
,那么就是遵循双亲委派规则,加载顺序如下:
/WEB-INF/classes /WEB-INF/lib/*.jap