Tomcat?Catalina為什么不new出來(lái)原理解析
一、Catalina為什么不new出來(lái)?
掌握了Java的類(lèi)加載器和雙親委派機(jī)制,現(xiàn)在我們就可以回答正題上來(lái)了,Tomcat的類(lèi)加載器是怎么設(shè)計(jì)的?
1.Web容器的特性
Web容器有其自身的特殊性,所以在類(lèi)加載器這塊是不能完全使用JVM的類(lèi)加載器的雙親委派機(jī)制的。在Web容器中我們應(yīng)該要滿(mǎn)足如下的特性:
隔離性:
部署在同一個(gè)Web容器上的兩個(gè)Web應(yīng)用程序所使用的Java類(lèi)庫(kù)可以實(shí)現(xiàn)相互隔離。設(shè)想一下,兩個(gè)Web應(yīng)用,一個(gè)使用了Spring3.0,另一個(gè)使用了新的的5.0,應(yīng)用服務(wù)器使用一個(gè)類(lèi)加載器,Web應(yīng)用將會(huì)因?yàn)閖ar包覆蓋而無(wú)法啟動(dòng)。

靈活性:
Web應(yīng)用之間的類(lèi)加載器相互獨(dú)立,那么就能針對(duì)一個(gè)Web應(yīng)用進(jìn)行重新部署,此時(shí)Web應(yīng)用的類(lèi)加載器會(huì)被重建,而且不會(huì)影響其他的Web應(yīng)用。如果采用一個(gè)類(lèi)加載器,類(lèi)之間的依賴(lài)是雜亂復(fù)雜的,無(wú)法完全移出某個(gè)應(yīng)用的類(lèi)。
性能:
性能也是一個(gè)比較重要的點(diǎn)。部署在同一個(gè)Web容器上的兩個(gè)Web應(yīng)用程序所使用的Java類(lèi)庫(kù)可以互相共享。這個(gè)需求也很常見(jiàn),例如,用戶(hù)可能有10個(gè)使用Spring框架的應(yīng)用程序部署在同一臺(tái)服務(wù)器上,如果把10份Spring分別存放在各個(gè)應(yīng)用程序的隔離目錄中,將會(huì)是很大的資源浪費(fèi)——這主要倒不是浪費(fèi)磁盤(pán)空間的問(wèn)題,而是指類(lèi)庫(kù)在使用時(shí)都要被加載到Web容器的內(nèi)存,如果類(lèi)庫(kù)不能共享,虛擬機(jī)的方法區(qū)就會(huì)很容易出現(xiàn)過(guò)度膨脹的風(fēng)險(xiǎn)。
2.Tomcat類(lèi)加載器結(jié)構(gòu)
明白了Web容器的類(lèi)加載器有多個(gè),再來(lái)看tomcat的類(lèi)加載器結(jié)構(gòu)。
首先上張圖,整體看下tomcat的類(lèi)加載器:

可以看到在原先的java類(lèi)加載器基礎(chǔ)上,tomcat新增了幾個(gè)類(lèi)加載器,包括3個(gè)基礎(chǔ)類(lèi)加載器和每個(gè)Web應(yīng)用的類(lèi)加載器,其中3個(gè)基礎(chǔ)類(lèi)加載器可在conf/catalina.properties中配置,具體介紹下:
Common:
以應(yīng)用類(lèi)加載器為父類(lèi),是tomcat頂層的公用類(lèi)加載器,其路徑由conf/catalina.properties中的common.loader指定,默認(rèn)指向${catalina.base}/lib下的包。

Catalina:
以Common類(lèi)加載器為父類(lèi),是用于加載Tomcat應(yīng)用服務(wù)器的類(lèi)加載器,其路徑由server.loader指定,默認(rèn)為空,此時(shí)tomcat使用Common類(lèi)加載器加載應(yīng)用服務(wù)器。

Shared:
以Common類(lèi)加載器為父類(lèi),是所有Web應(yīng)用的父類(lèi)加載器,其路徑由shared.loader指定,默認(rèn)為空,此時(shí)tomcat使用Common類(lèi)加載器作為Web應(yīng)用的父加載器。
Web應(yīng)用:
以Shared類(lèi)加載器為父類(lèi),加載/WEB-INF/classes目錄下的未壓縮的Class和資源文件以及/WEB-INF/lib目錄下的jar包,該類(lèi)加載器只對(duì)當(dāng)前Web應(yīng)用可見(jiàn),對(duì)其他Web應(yīng)用均不可見(jiàn)。
默認(rèn)情況下,Common、Catalina、Shared類(lèi)加載器是同一個(gè),但可以配置3個(gè)不同的類(lèi)加載器,使他們各司其職。
首先,Common類(lèi)加載器復(fù)雜加載Tomcat應(yīng)用服務(wù)器內(nèi)部和Web應(yīng)用均可見(jiàn)的類(lèi),如Servlet規(guī)范相關(guān)包和一些通用工具包。
其次,Catalina類(lèi)加載器負(fù)責(zé)只有Tomcat應(yīng)用服務(wù)器內(nèi)部可見(jiàn)的類(lèi),這些類(lèi)對(duì)Web應(yīng)用不可見(jiàn)。比如,想實(shí)現(xiàn)自己的會(huì)話存儲(chǔ)方案,而且該方案依賴(lài)了一些第三方包,當(dāng)然是不希望這些包對(duì)Web應(yīng)用可見(jiàn),這時(shí)可以配置server.load,創(chuàng)建獨(dú)立的Catalina類(lèi)加載器。
再次,Shared類(lèi)復(fù)雜加載Web應(yīng)用共享類(lèi),這些類(lèi)tomcat服務(wù)器不會(huì)依賴(lài)
3.Tomcat源碼分析
3.1 CatalinClassLoader
首先來(lái)看看Tomcat的類(lèi)加載器的繼承結(jié)構(gòu)

可以看到繼承的結(jié)構(gòu)和我們上面所寫(xiě)的類(lèi)加載器的結(jié)構(gòu)不同。
大家需要注意雙親委派機(jī)制并不是通過(guò)繼承來(lái)實(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#init()方法。init方法會(huì)調(diào)用initClassLoaders,同樣也會(huì)將Catalina ClassLoader設(shè)置到當(dāng)前線程設(shè)置到當(dāng)前線程,進(jìn)入initClassLoaders來(lái)看看。
private void initClassLoaders() {
try {
// 創(chuàng)建 commonLoader catalinaLoader sharedLoader
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();
}
// 默認(rèn)情況下 server.loader 和 shared.loader 都為空則會(huì)返回 commonLoader 類(lèi)加載器
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
我們可以看到在initClassLoaders()方法中完成了CommonClassLoader, CatalinaClassLoader,和SharedClassLoader的創(chuàng)建,而且進(jìn)入到createClassLoader方法中。

可以看到這三個(gè)基礎(chǔ)類(lèi)加載器所加載的資源剛好對(duì)應(yīng)conf/catalina.properties中的common.loader,server.loader,shared.loader
3.2 層次結(jié)構(gòu)
Common/Catalina/Shared ClassLoader的創(chuàng)建好了之后就會(huì)維護(hù)相互之間的組合關(guān)系

其實(shí)也就是設(shè)置了父加載器
3.3 具體的加載過(guò)程
源碼比較長(zhǎng),直接進(jìn)入到 WebappClassLoaderBase中的 LoadClass方法
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled()) {
log.debug("loadClass(" + name + ", " + resolve + ")");
}
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
// 檢查WebappClassLoader中是否加載過(guò)此類(lèi)
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.1) Check our previously loaded class cache
// 如果第一步?jīng)]有找到,則繼續(xù)檢查JVM虛擬機(jī)中是否加載過(guò)該類(lèi)
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.2) Try loading the class with the bootstrap class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
// 如果前兩步都沒(méi)有找到,則使用系統(tǒng)類(lèi)加載該類(lèi)(也就是當(dāng)前JVM的ClassPath)。
// 為了防止覆蓋基礎(chǔ)類(lèi)實(shí)現(xiàn),這里會(huì)判斷class是不是JVMSE中的基礎(chǔ)類(lèi)庫(kù)中類(lèi)。
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
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) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
// 檢查是否 設(shè)置了delegate屬性,設(shè)置為true,那么就會(huì)完全按照J(rèn)VM的"雙親委托"機(jī)制流程加載類(lèi)。
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
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
}
}
// (2) Search local repositories
// 若是沒(méi)有委托,則默認(rèn)會(huì)首次使用WebappClassLoader來(lái)加載類(lèi)。通過(guò)自定義findClass定義處理類(lèi)加載規(guī)則。
// findClass()會(huì)去Web-INF/classes 目錄下查找類(lèi)。
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
}
// (3) Delegate to parent unconditionally
// 若是WebappClassLoader在/WEB-INF/classes、/WEB-INF/lib下還是查找不到class,
// 那么無(wú)條件強(qiáng)制委托給System、Common類(lèi)加載器去查找該類(lèi)。
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);
}
Web應(yīng)用類(lèi)加載器默認(rèn)的加載順序是:
- (1).先從緩存中加載;
- (2).如果沒(méi)有,則從JVM的Bootstrap類(lèi)加載器加載;
- (3).如果沒(méi)有,則從當(dāng)前類(lèi)加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序);
- (4).如果沒(méi)有,則從父類(lèi)加載器加載,由于父類(lè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類(lèi)加載器加載;
- (3).如果沒(méi)有,則從父類(lèi)加載器加載,加載順序是AppClassLoader、Common、Shared。
- (4).如果沒(méi)有,則從當(dāng)前類(lèi)加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序)
以上就是Tomcat Catalina為什么不new出來(lái)原理解析的詳細(xì)內(nèi)容,更多關(guān)于Tomcat Catalina原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Tomcat配置HTTPS訪問(wèn)的實(shí)現(xiàn)步驟
本文主要介紹了Tomcat配置HTTPS訪問(wèn)的實(shí)現(xiàn)步驟,在tomcat中存在兩種證書(shū)驗(yàn)證情況單向驗(yàn)證和雙向驗(yàn)證,下面就詳細(xì)的介紹一下這兩種情況的配置,感興趣的可以了解一下2022-07-07
cemtos 7 linux 安裝與卸載 tomcat 7的教程
這篇文章主要介紹了cemtos 7 linux 安裝與卸載 tomcat 7的教程,需要的朋友可以參考下2017-10-10
Tomcat實(shí)現(xiàn)多域名訪問(wèn)詳解
這篇文章主要介紹了Tomcat多域名訪問(wèn)詳解,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
tomcat+nginx實(shí)現(xiàn)多應(yīng)用部署的示例代碼
本文主要介紹了tomcat+nginx實(shí)現(xiàn)多應(yīng)用部署的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Tomcat管理平臺(tái)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Tomcat管理平臺(tái)的相關(guān)資料,講解Tomcat服務(wù)器的管理平臺(tái)具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
war包部署到Tomcat下運(yùn)行的實(shí)現(xiàn)步驟
這篇文章主要介紹了war包部署到Tomcat下運(yùn)行的實(shí)現(xiàn)步驟,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Tomcat中修改server.xml和content.xml后自動(dòng)還原問(wèn)題解決
當(dāng)我們?cè)谔幚碇形膩y碼或是配置數(shù)據(jù)源時(shí),我們要修改Tomcat下的server.xml和content.xml文件。但是當(dāng)我們修改完后重啟Tomcat服務(wù)器時(shí)發(fā)現(xiàn)xml文件又被還原了,修改無(wú)效果。本文就來(lái)解決一下2021-05-05
使用jenkins將項(xiàng)目部署到另一臺(tái)主機(jī)的過(guò)程
這篇文章主要介紹了使用jenkins將項(xiàng)目部署到另一臺(tái)主機(jī)的詳細(xì)過(guò)程,這這里手動(dòng)部署需要關(guān)閉防火墻,確保git命令存在,拉取部署的項(xiàng)目包,具體實(shí)例代碼跟隨小編一起看看吧2021-10-10
tomcat6.0 /7.0安裝版內(nèi)存溢出設(shè)置方法
這篇文章主要介紹了tomcat6.0 /7.0安裝版內(nèi)存溢出設(shè)置方法,需要的朋友可以參考下2014-07-07

