在學習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
雙親委派模型工做過程是:若是收到一個類加載的請求,自己不會先加載此類,而是會先將此請求委派給父類加載器去完成,每一個層次都是如此,直到啓動類加載器中,只有父類都沒有加載此文件,那麼子類纔會嘗試本身去加載。架構
爲何要設置雙親委派模型呢?實際上是爲了保證Java程序的穩定運行,例如Object類,它是存放在rt.jar
中,不管哪個類加載器要加載Object類,最終都會委託給頂層的BootStrapClassLoader,因此全部的類中使用的Object都是同一個類,相反若是沒有雙親委派模型的話,那麼隨意一個類加載器均可以定義一個新的Object類,那麼應用程序將會變得很是混亂。其實雙親委派模型代碼很是簡單。實如今ClassLoader中的loadClass方法下。app
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
中的jar包中的類。若是在配置文件中配置了<Loader delegate="true"/>
,那麼就是遵循雙親委派規則,加載順序以下:
/WEB-INF/classes
中的類。/WEB-INF/lib/*.jap
中的jar包中的類。