本章結(jié)構(gòu)如下:
- 前言
- Java類加載機(jī)制
- tomcat類加載器
- tomcat類加載器源碼分析
一、前言
下載tomcat解壓后,可以在webapps目錄下看到幾個(gè)文件夾(這些都是web應(yīng)用),webapps對(duì)應(yīng)到tomcat容器中的Host,里面的文件夾則對(duì)應(yīng)到Context。tomcat啟動(dòng)后,webapps下的所有web應(yīng)用都可以提供服務(wù)。
那么就有一個(gè)問(wèn)題,假如webapps下有兩個(gè)應(yīng)用app1和app2,它們有各自獨(dú)立依賴的jar包,又有共同依賴的jar包,這些相同的jar包有些版本相同,有些又不相同,這種情況下,tomcat是如何加載這些jar包的呢?
帶著這個(gè)疑問(wèn),一步步來(lái)分析tomcat的類加載機(jī)制吧。
二、Java類加載機(jī)制
在這之前,當(dāng)然要先了解一下java中類加載時(shí)怎樣的,畢竟tomcat是用java寫的,它的加載機(jī)制也是基于java的類加載機(jī)制。
2.1、類加載器
1.什么是類加載器?
虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的“通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。
2.如何判斷兩個(gè)類是否相等?
類加載器用于實(shí)現(xiàn)類的加載動(dòng)作。對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身共同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類,都擁有一個(gè)獨(dú)立的類名稱空間。也就是說(shuō):比較兩個(gè)類是否“相等”,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義。否則,即使這兩個(gè)類來(lái)源于同一個(gè)Class文件,被同一個(gè)虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等。
2.2、雙親委派模型
Java 提供三種類型的系統(tǒng)類加載器:
- 啟動(dòng)類加載器(Bootstrap ClassLoader):由C++語(yǔ)言實(shí)現(xiàn),屬于JVM的一部分,其作用是加載 <JAVA_HOME>\lib 目錄中的文件,或者被-Xbootclasspath參數(shù)所指定的路徑中的文件,并且該類加載器只加載特定名稱的文件(如 rt.jar),而不是該目錄下所有的文件。啟動(dòng)類加載器無(wú)法被Java程序直接引用。
- 擴(kuò)展類加載器(Extension ClassLoader):由sun.misc.Launcher.ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù),開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。
- 應(yīng)用程序類加載器(Application ClassLoader):也稱系統(tǒng)類加載器,由sun.misc.Launcher.AppClassLoader實(shí)現(xiàn)。負(fù)責(zé)加載用戶類路徑(Class Path)上所指定的類庫(kù),開(kāi)發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
應(yīng)用程序都是由這3種類加載器互相配合進(jìn)行加載的,如果有必要,還可以加入自己定義的類加載器。加載流程如下圖所示:
用一段代碼測(cè)試一下:
public static void main(String[] args) {
ClassLoader loader = Xxx.class.getClassLoader();
while (loader!=null){
System.out.println(loader);
loader = loader.getParent();
}
}
結(jié)果:
從結(jié)果我們可以看出,默認(rèn)情況下,用戶自定義的類使用 AppClassLoader 加載,AppClassLoader 的父加載器為 ExtClassLoader,但是 ExtClassLoader 的父加載器卻顯示為空,這是什么原因呢?究其緣由,啟動(dòng)類加載器屬于 JVM 的一部分,它不是由 Java 語(yǔ)言實(shí)現(xiàn)的,在 Java 中無(wú)法直接引用,所以才返回空。
java這種類加載層級(jí)稱為雙親委派模型。它的工作過(guò)程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒(méi)有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載。
為什么要這樣呢?
都知道java.lang.Object是java中所有類的父類,它存放在rt.jar之中,按照雙親委派模型,無(wú)論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。試想,如果沒(méi)有使用雙親委派模型,由各個(gè)類加載器自行去加載,顯然,這就存在很大風(fēng)險(xiǎn),用戶完全可以惡意編寫一個(gè)java.lang.Object類,然后放到ClassPath下,那系統(tǒng)就會(huì)出現(xiàn)多個(gè)Object類,Java類型體系中最基礎(chǔ)的行為也就無(wú)法保證,應(yīng)用程序也將會(huì)變得一片混亂。
2.3、簡(jiǎn)單看下源碼
protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(var1)) {
//首先, 檢查請(qǐng)求的類是否已經(jīng)被加載過(guò)了
Class var4 = this.findLoadedClass(var1);
if(var4 == null) {
long var5 = System.nanoTime();
try {
if(this.parent != null) {
var4 = this.parent.loadClass(var1, false);
} else {
var4 = this.findBootstrapClassOrNull(var1);
}
} catch (ClassNotFoundException var10) {
//如果父類加載器拋出ClassNotFoundException
//說(shuō)明父類加載器無(wú)法完成加載請(qǐng)求
;
}
if(var4 == null) {
long var7 = System.nanoTime();
//在父類加載器無(wú)法加載的時(shí)候
//再調(diào)用本身的findClass方法來(lái)進(jìn)行類加載
var4 = this.findClass(var1);
PerfCounter.getParentDelegationTime().addTime(var7 - var5);
PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
PerfCounter.getFindClasses().increment();
}
}
if(var2) {
this.resolveClass(var4);
}
return var4;
}
}
從源碼可以看出,ExtClassLoader 和 AppClassLoader都繼承自 ClassLoader 類,ClassLoader 類中通過(guò) loadClass 方法來(lái)實(shí)現(xiàn)雙親委派機(jī)制。整個(gè)類的加載過(guò)程可分為如下三步:
- 查找對(duì)應(yīng)的類是否已經(jīng)加載。
- 若未加載,則判斷當(dāng)前類加載器的父加載器是否為空,不為空則委托給父類去加載,否則調(diào)用啟動(dòng)類加載器加載(findBootstrapClassOrNull 再往下會(huì)調(diào)用一個(gè) native 方法)。
- 若第二步加載失敗,說(shuō)明父類加載器無(wú)法完成加載請(qǐng)求 ,則調(diào)用當(dāng)前類加載器加載。
詳細(xì)可以參考這篇博文:
2.4、打破雙親委派模型(來(lái)自《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐》)
雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是Java設(shè)計(jì)者推薦給開(kāi)發(fā)者的類加載器實(shí)現(xiàn)方式。
它很好地解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問(wèn)題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a調(diào)用的API,但世事往往沒(méi)有絕對(duì)的完美,如果基礎(chǔ)類又要調(diào)用回用戶的代碼,那該怎么辦?
這并非是不可能的事情,一個(gè)典型的例子便是JNDI服務(wù),它的代碼由啟動(dòng)類加載器去加載(在JDK1.3時(shí)放進(jìn)rt.jar),但JNDI的目的就是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啟動(dòng)類加載器不可能“認(rèn)識(shí)”之些代碼,該怎么辦?
Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過(guò)java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒(méi)有設(shè)置過(guò)的話,那這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器(Application ClassLoader)。
有了線程上下文類加載器,就可以做一些“舞弊”的事情了,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載的動(dòng)作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來(lái)逆向使用類加載器,實(shí)際上已經(jīng)違背了雙親委派模型的一般性原則。
還有“被破壞”是由于用戶對(duì)程序的動(dòng)態(tài)性的追求導(dǎo)致的,例如OSGi的出現(xiàn)。在OSGi環(huán)境下,類加載器不再是雙親委派模型中的樹(shù)狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為網(wǎng)狀結(jié)構(gòu)。
當(dāng)收到類加載請(qǐng)求時(shí),OSGi將按照下面的順序進(jìn)行類搜索:
1)將以java.*開(kāi)頭的類委派給父類加載器加載。
2)否則,將委派列表名單內(nèi)的類委派給父類加載器加載。
3)否則,將Import列表中的類委派給Export這個(gè)類的Bundle的類加載器加載。
4)否則,查找當(dāng)前Bundle的Class Path,使用自己的類加載器加載。
5)否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給Fragment Bundle的類加載器加載。
6)否則,查找Dynamic Import列表的Bundle,委派給對(duì)應(yīng)Bundle的類加載器加載。
7)否則,類查找失敗。
三、tomcat類加載器
了解了java的雙親委派模型,現(xiàn)在回到正題上,tomcat的類加載器是怎么樣的?
3.1、Web容器應(yīng)該具備的特性
不難想象,一個(gè)功能健全的Web容器,它的類加載器必然有多個(gè),因?yàn)樗鼞?yīng)該具備如下特性:
- 隔離性:部署在同一個(gè)Web容器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以實(shí)現(xiàn)相互隔離。設(shè)想一下,兩個(gè)Web應(yīng)用,一個(gè)使用了Spring2.5,另一個(gè)使用了教新的4.0,應(yīng)用服務(wù)器使用一個(gè)類加載器,Web應(yīng)用將會(huì)因?yàn)閖ar包覆蓋而無(wú)法啟動(dòng)。
- 靈活性:Web應(yīng)用之間的類加載器相互獨(dú)立,那么就能針對(duì)一個(gè)Web應(yīng)用進(jìn)行重新部署,此時(shí)Web應(yīng)用的類加載器會(huì)被重建,而且不會(huì)影響其他的Web應(yīng)用。如果采用一個(gè)類加載器,類之間的依賴是雜亂復(fù)雜的,無(wú)法完全移出某個(gè)應(yīng)用的類。
- 性能:部署在同一個(gè)Web容器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以互相共享。這個(gè)需求也很常見(jiàn),例如,用戶可能有10個(gè)使用Spring組織的應(yīng)用程序部署在同一臺(tái)服務(wù)器上,如果把10份Spring分別存放在各個(gè)應(yīng)用程序的隔離目錄中,將會(huì)是很大的資源浪費(fèi)——這主要倒不是浪費(fèi)磁盤空間的問(wèn)題,而是指類庫(kù)在使用時(shí)都要被加載到Web容器的內(nèi)存,如果類庫(kù)不能共享,虛擬機(jī)的方法區(qū)就會(huì)很容易出現(xiàn)過(guò)度膨脹的風(fēng)險(xiǎn)。
- ...
3.2、tomcat類加載器結(jié)構(gòu)
了解了一款Web容器應(yīng)該具備的特性,明白了Web容器的類加載器有多個(gè),再來(lái)看tomcat的類加載器結(jié)構(gòu)。
首先上張圖,整體看下tomcat的類加載器:
可以看到在原先的java類加載器基礎(chǔ)上,tomcat新增了幾個(gè)類加載器,包括3個(gè)基礎(chǔ)類加載器和每個(gè)Web應(yīng)用的類加載器,其中3個(gè)基礎(chǔ)類加載器可在conf/catalina.properties中配置,具體介紹下:
- Common:以應(yīng)用類加載器為父類,是tomcat頂層的公用類加載器,其路徑由conf/catalina.properties中的common.loader指定,默認(rèn)指向${catalina.home}/lib下的包。
- Catalina:以Common類加載器為父類,是用于加載Tomcat應(yīng)用服務(wù)器的類加載器,其路徑由server.loader指定,默認(rèn)為空,此時(shí)tomcat使用Common類加載器加載應(yīng)用服務(wù)器。
- Shared:以Common類加載器為父類,是所有Web應(yīng)用的父類加載器,其路徑由shared.loader指定,默認(rèn)為空,此時(shí)tomcat使用Common類加載器作為Web應(yīng)用的父加載器。
- Web應(yīng)用:以Shared類加載器為父類,加載/WEB-INF/classes目錄下的未壓縮的Class和資源文件以及/WEB-INF/lib目錄下的jar包,該類加載器只對(duì)當(dāng)前Web應(yīng)用可見(jiàn),對(duì)其他Web應(yīng)用均不可見(jiàn)。
默認(rèn)情況下,Common、Catalina、Shared類加載器是同一個(gè),但可以配置3個(gè)不同的類加載器,使他們各司其職。
首先,Common類加載器復(fù)雜加載Tomcat應(yīng)用服務(wù)器內(nèi)部和Web應(yīng)用均可見(jiàn)的類,如Servlet規(guī)范相關(guān)包和一些通用工具包。
其次,Catalina類加載器負(fù)責(zé)只有Tomcat應(yīng)用服務(wù)器內(nèi)部可見(jiàn)的類,這些類對(duì)Web應(yīng)用不可見(jiàn)。比如,想實(shí)現(xiàn)自己的會(huì)話存儲(chǔ)方案,而且該方案依賴了一些第三方包,當(dāng)然是不希望這些包對(duì)Web應(yīng)用可見(jiàn),這時(shí)可以配置server.load,創(chuàng)建獨(dú)立的Catalina類加載器。
再次,Shared類復(fù)雜加載Web應(yīng)用共享類,這些類tomcat服務(wù)器不會(huì)依賴。
相信看到這,引言中的疑問(wèn)已經(jīng)解開(kāi)了吧。
那還有一個(gè)問(wèn)題,如果有10個(gè)Web應(yīng)用程序都是用Spring來(lái)進(jìn)行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。Spring要對(duì)用戶程序的類進(jìn)行管理,自然要能訪問(wèn)到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的,那么被CommonClassLoader或SharedClassLoader加載的Spring如何訪問(wèn)并不在其加載范圍內(nèi)的用戶程序呢?
如果按主流的雙親委派機(jī)制,顯然無(wú)法做到讓父類加載器加載的類去訪問(wèn)子類加載器加載的類,但使用線程上下文加載器,可以讓父類加載器請(qǐng)求子類加載器去完成類加載的動(dòng)作。spring加載類所用的Classloader是通過(guò)Thread.currentThread().getContextClassLoader()來(lái)獲取的,而當(dāng)線程創(chuàng)建時(shí)會(huì)默認(rèn)setContextClassLoader(AppClassLoader),即線程上下文類加載器被設(shè)置為AppClassLoader,spring中始終可以獲取到這個(gè)AppClassLoader(在Tomcat里就是WebAppClassLoader)子類加載器來(lái)加載bean,以后任何一個(gè)線程都可以通過(guò)getContextClassLoader()獲取到WebAppClassLoader來(lái)getbean了。
接下來(lái),看一看源碼。
四、tomcat類加載器源碼分析
1.Common/Catalina/Shared ClassLoader的創(chuàng)建
首先先看下tomcat的類加載器繼承結(jié)構(gòu):
相信看到這會(huì)很疑惑,這和上面介紹的tomcat類加載器結(jié)構(gòu)不一樣啊。
是這樣的,雙親委派模型本不是通過(guò)繼承實(shí)現(xiàn)的,而是組合,所以AppClassLoader沒(méi)有繼承自ExtClassLoader,WebappClassLoader也沒(méi)有繼承自AppClassLoader。至于Common ClassLoader,Shared ClassLoader,Catalina ClassLoader則是在啟動(dòng)時(shí)初始化的三個(gè)不同名字的URLClassLoader。
來(lái)到BootStrap中看一下:
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
...
}
先判斷Bootstrap是否為null,不為null,直接將Catalina ClassLoader設(shè)置到當(dāng)前線程,用于加載服務(wù)器相關(guān)類,為null則進(jìn)入bootstrap的init方法。
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
...
}
init方法會(huì)調(diào)用initClassLoaders,同樣也會(huì)將Catalina ClassLoader設(shè)置到當(dāng)前線程設(shè)置到當(dāng)前線程,進(jìn)入initClassLoaders來(lái)看看。
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
應(yīng)該就很明白了,會(huì)創(chuàng)建三個(gè)ClassLoader,CommClassLoader,Catalina ClassLoader,SharedClassLoader,正好對(duì)應(yīng)前面介紹的三個(gè)基礎(chǔ)類加載器。
再進(jìn)入createClassLoader可以看到這三個(gè)基礎(chǔ)類加載器所加載的資源剛好對(duì)應(yīng)conf/catalina.properties中的common.loader,server.loader,shared.loader:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(
new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(
new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(
new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
2.Common/Catalina/Shared ClassLoader的層次構(gòu)建
Common/Catalina/Shared ClassLoader的創(chuàng)建好了,肯定是要被使用的,是在哪里使用的呢?它們之間同Webapp ClassLoader又是怎么聯(lián)系起來(lái)的?
既然sharedClassLoader被傳入到Catalina中,就來(lái)看它的getParentClassLoader調(diào)用棧。
經(jīng)過(guò)層層調(diào)用,來(lái)帶StandardContext的startInternal方法,這個(gè)方法很長(zhǎng)很復(fù)雜,就不全貼出來(lái),里面有這樣一段:
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
它會(huì)創(chuàng)建WebappLoader對(duì)象,并通過(guò)setLoader(webappLoader)賦值到一個(gè)實(shí)例變量中,然后會(huì)調(diào)用WebappLoader的start方法:
...
if (ok) {
// Start our subordinate components, if any
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
...
}
...
這里關(guān)系到tomcat的生命周期機(jī)制,先不糾結(jié),直接找到start方法,start方法是在父類中,最后要調(diào)回到WebappLoader中的startInternal方法。
該方法中有這樣一段:
...
classLoader = createClassLoader();
classLoader.setResources(context.getResources());
classLoader.setDelegate(this.delegate);
...
進(jìn)入createClassLoader方法:
private WebappClassLoaderBase createClassLoader()
throws Exception {
// private String loaderClass = ParallelWebappClassLoader.class.getName();
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = context.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
return classLoader;
}
該方法會(huì)實(shí)例化一個(gè)ParallelWebappClassLoader實(shí)例,并且傳遞了sharedLoader作為其父親加載器。
代碼閱讀到這里,已經(jīng)基本清楚了Tomcat中ClassLoader的總體結(jié)構(gòu),總結(jié)如下: 在Tomcat存在common、cataina、shared三個(gè)公共的classloader,默認(rèn)情況下,這三個(gè)classloader其實(shí)是同一個(gè),都是common classloader,而針對(duì)每個(gè)webapp,也就是context(對(duì)應(yīng)代碼中的StandardContext類),都有自己的WebappClassLoader實(shí)例來(lái)加載每個(gè)應(yīng)用自己的類,該類加載實(shí)例的parent即是Shared ClassLoader。
這樣前面關(guān)于tomcat的類加載層次應(yīng)該就清楚起來(lái)了。
3.tomcat類加載器的加載過(guò)程
前面介紹了tomcat類加載器的創(chuàng)建及層次,下面進(jìn)入本篇最后一點(diǎn)內(nèi)容,這些類加載器是怎樣加載類的呢?
所以重點(diǎn)看ParallelWebappClassLoader(看名字就是并行的WebappClassLoader,具體的差異就不做研究了)。
ParallelWebappClassLoader的loadClass是在其父類WebappClassLoaderBase中實(shí)現(xiàn)的:
第一步:
首先調(diào)用findLoaderClass0() 方法檢查WebappClassLoader中是否加載過(guò)此類。
WebappClassLoader 加載過(guò)的類都存放在 resourceEntries 緩存中。protected final Map<String, ResourceEntry> resourceEntries = new ConcurrentHashMap<>();
第二步:
如果第一步?jīng)]有找到,則繼續(xù)檢查JVM虛擬機(jī)中是否加載過(guò)該類。
調(diào)用ClassLoader的findLoadedClass()方法檢查。
第三步:
如果前兩步都沒(méi)有找到,則使用系統(tǒng)類加載該類(也就是當(dāng)前JVM的ClassPath)。為了防止覆蓋基礎(chǔ)類實(shí)現(xiàn),這里會(huì)判斷class是不是JVMSE中的基礎(chǔ)類庫(kù)中類。
protected ClassLoader getJavaseClassLoader() {
return javaseClassLoader;
}
第四步:
先判斷是否設(shè)置了delegate屬性,設(shè)置為true,那么就會(huì)完全按照J(rèn)VM的"雙親委托"機(jī)制流程加載類。
若是默認(rèn)的話,是先使用WebappClassLoader自己處理加載類的。當(dāng)然,若是委托了,使用雙親委托亦沒(méi)有加載到class實(shí)例,那還是最后使用WebappClassLoader加載。
第五步:
若是沒(méi)有委托,則默認(rèn)會(huì)首次使用WebappClassLoader來(lái)加載類。通過(guò)自定義findClass定義處理類加載規(guī)則。
findClass()會(huì)去Web-INF/classes 目錄下查找類。
去閱讀里面的代碼,里面有這樣一個(gè)方法:
@Override
public WebResource getClassLoaderResource(String path) {
return getResource("/WEB-INF/classes" + path, true, true);
}
第六步:
若是WebappClassLoader在/WEB-INF/classes、/WEB-INF/lib下還是查找不到class,那么無(wú)條件強(qiáng)制委托給System、Common類加載器去查找該類。
最后借tomcat官網(wǎng)上的話總結(jié)一下:
Web應(yīng)用類加載器默認(rèn)的加載順序是:
(1).先從緩存中加載;
(2).如果沒(méi)有,則從JVM的Bootstrap類加載器加載;
(3).如果沒(méi)有,則從當(dāng)前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序);
(4).如果沒(méi)有,則從父類加載器加載,由于父類加載器采用默認(rèn)的委派模式,所以加載順序是AppClassLoader、Common、Shared。
tomcat提供了delegate屬性用于控制是否啟用java委派模式,默認(rèn)false(不啟用),當(dāng)設(shè)置為true時(shí),tomcat將使用java的默認(rèn)委派模式,這時(shí)加載順序如下:
(1).先從緩存中加載;
(2).如果沒(méi)有,則從JVM的Bootstrap類加載器加載;
(3).如果沒(méi)有,則從父類加載器加載,加載順序是AppClassLoader、Common、Shared。
(4).如果沒(méi)有,則從當(dāng)前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序);