在學習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包中的類。