在学习Tomcat中的类加载器,而且Tomcat为何要实现本身的类加载器打破双亲委派模型缘由以前,咱们首先须要知道Java中定义的类加载器是什么,双亲委派模型是什么。html
类加载器负责在程序运行时将java文件动态加载到JVM中java
从Java虚拟机的角度来说的话,存在两种不一样的类加载器:web
启动类加载器(Bootstrap ClassLoader):这个类加载器是使用C++语言实现的,是虚拟机自身的一部分。apache
其余的类加载器:这些类加载器都由Java语言实现,独立于虚拟机外部,而且全都继承自抽象类java.lang.ClassLoader
,其中其余类加载器大概又分为bootstrap
ExtClassLoader
实现,它负责加载JAVA_HOME/lib/ext
目录中的全部类,或者被java.ext.dir
系统变量所指定的路径中全部的类。AppClassLoader
实现的,它负责加载用户类路径(ClassPath)上所指定的全部类,若是应用中没有自定义本身的类加载器,那么通常状况就是程序中默认的类加载器。对于任意一个类,都须要由加载它的类加载器和这个类自己一同确立其在Java虚拟机中的惟一性缓存
上图中展现的层次结构,称之为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余加载器都应该有本身的父加载器。这里的父子关系不是经过继承来实现的,而是经过设置parent
变量来实现的。tomcat
双亲委派模型工做过程是:若是收到一个类加载的请求,自己不会先加载此类,而是会先将此请求委派给父类加载器去完成,每一个层次都是如此,直到启动类加载器中,只有父类都没有加载此文件,那么子类才会尝试本身去加载。bash
为何要设置双亲委派模型呢?实际上是为了保证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
方法是须要子类本身去实现的逻辑。app
下面的简图是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
中的jar包中的类。若是在配置文件中配置了<Loader delegate="true"/>
,那么就是遵循双亲委派规则,加载顺序以下:
/WEB-INF/classes
中的类。/WEB-INF/lib/*.jap
中的jar包中的类。