您好,登錄后才能下訂單哦!
Tomcat中的類加載器怎么用,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
在學習Tomcat中的類加載器,并且Tomcat為什么要實現自己的類加載器打破雙親委派模型原因之前,我們首先需要知道Java中定義的類加載器是什么,雙親委派模型是什么。
類加載器負責在程序運行時將java文件動態加載到JVM中
從Java虛擬機的角度來講的話,存在兩種不同的類加載器:
啟動類加載器(Bootstrap ClassLoader):這個類加載器是使用C++語言實現的,是虛擬機自身的一部分。
其他的類加載器:這些類加載器都由Java語言實現,獨立于虛擬機外部,并且全都繼承自抽象類java.lang.ClassLoader
,其中其他類加載器大概又分為
ExtensionClassLoader:這個類加載器由ExtClassLoader
實現,它負責加載JAVA_HOME/lib/ext
目錄中的所有類,或者被java.ext.dir
系統變量所指定的路徑中所有的類。
ApplicationClassLoader:這個類加載器是由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 ..
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) { 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應用默認的類加載順序是(打破了雙親委派規則):
先從JVM的BootStrapClassLoader中加載。
加載Web應用下/WEB-INF/classes
中的類。
加載Web應用下/WEB-INF/lib/*.jap
中的jar包中的類。
加載上面定義的System路徑下面的類。
加載上面定義的Common路徑下面的類。
如果在配置文件中配置了<Loader delegate="true"/>
,那么就是遵循雙親委派規則,加載順序如下:
先從JVM的BootStrapClassLoader中加載。
加載上面定義的System路徑下面的類。
加載上面定義的Common路徑下面的類。
加載Web應用下/WEB-INF/classes
中的類。
加載Web應用下/WEB-INF/lib/*.jap
中的jar包中的類。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。