Tomcat Catalina為什么不new出來原理解析
一、Catalina為什么不new出來?
掌握了Java的類加載器和雙親委派機(jī)制,現(xiàn)在我們就可以回答正題上來了,Tomcat的類加載器是怎么設(shè)計的?
1.Web容器的特性
Web容器有其自身的特殊性,所以在類加載器這塊是不能完全使用JVM的類加載器的雙親委派機(jī)制的。在Web容器中我們應(yīng)該要滿足如下的特性:
隔離性:
部署在同一個Web容器上的兩個Web應(yīng)用程序所使用的Java類庫可以實(shí)現(xiàn)相互隔離。設(shè)想一下,兩個Web應(yīng)用,一個使用了Spring3.0,另一個使用了新的的5.0,應(yīng)用服務(wù)器使用一個類加載器,Web應(yīng)用將會因?yàn)閖ar包覆蓋而無法啟動。
靈活性:
Web應(yīng)用之間的類加載器相互獨(dú)立,那么就能針對一個Web應(yīng)用進(jìn)行重新部署,此時Web應(yīng)用的類加載器會被重建,而且不會影響其他的Web應(yīng)用。如果采用一個類加載器,類之間的依賴是雜亂復(fù)雜的,無法完全移出某個應(yīng)用的類。
性能:
性能也是一個比較重要的點(diǎn)。部署在同一個Web容器上的兩個Web應(yīng)用程序所使用的Java類庫可以互相共享。這個需求也很常見,例如,用戶可能有10個使用Spring框架的應(yīng)用程序部署在同一臺服務(wù)器上,如果把10份Spring分別存放在各個應(yīng)用程序的隔離目錄中,將會是很大的資源浪費(fèi)——這主要倒不是浪費(fèi)磁盤空間的問題,而是指類庫在使用時都要被加載到Web容器的內(nèi)存,如果類庫不能共享,虛擬機(jī)的方法區(qū)就會很容易出現(xiàn)過度膨脹的風(fēng)險。
2.Tomcat類加載器結(jié)構(gòu)
明白了Web容器的類加載器有多個,再來看tomcat的類加載器結(jié)構(gòu)。
首先上張圖,整體看下tomcat的類加載器:
可以看到在原先的java類加載器基礎(chǔ)上,tomcat新增了幾個類加載器,包括3個基礎(chǔ)類加載器和每個Web應(yīng)用的類加載器,其中3個基礎(chǔ)類加載器可在conf/catalina.properties中配置,具體介紹下:
Common:
以應(yīng)用類加載器為父類,是tomcat頂層的公用類加載器,其路徑由conf/catalina.properties中的common.loader指定,默認(rèn)指向${catalina.base}/lib下的包。
Catalina:
以Common類加載器為父類,是用于加載Tomcat應(yīng)用服務(wù)器的類加載器,其路徑由server.loader指定,默認(rèn)為空,此時tomcat使用Common類加載器加載應(yīng)用服務(wù)器。
Shared:
以Common類加載器為父類,是所有Web應(yīng)用的父類加載器,其路徑由shared.loader指定,默認(rèn)為空,此時tomcat使用Common類加載器作為Web應(yīng)用的父加載器。
Web應(yīng)用:
以Shared類加載器為父類,加載/WEB-INF/classes目錄下的未壓縮的Class和資源文件以及/WEB-INF/lib目錄下的jar包,該類加載器只對當(dāng)前Web應(yīng)用可見,對其他Web應(yīng)用均不可見。
默認(rèn)情況下,Common、Catalina、Shared類加載器是同一個,但可以配置3個不同的類加載器,使他們各司其職。
首先,Common類加載器復(fù)雜加載Tomcat應(yīng)用服務(wù)器內(nèi)部和Web應(yīng)用均可見的類,如Servlet規(guī)范相關(guān)包和一些通用工具包。
其次,Catalina類加載器負(fù)責(zé)只有Tomcat應(yīng)用服務(wù)器內(nèi)部可見的類,這些類對Web應(yīng)用不可見。比如,想實(shí)現(xiàn)自己的會話存儲方案,而且該方案依賴了一些第三方包,當(dāng)然是不希望這些包對Web應(yīng)用可見,這時可以配置server.load,創(chuàng)建獨(dú)立的Catalina類加載器。
再次,Shared類復(fù)雜加載Web應(yīng)用共享類,這些類tomcat服務(wù)器不會依賴
3.Tomcat源碼分析
3.1 CatalinClassLoader
首先來看看Tomcat的類加載器的繼承結(jié)構(gòu)
可以看到繼承的結(jié)構(gòu)和我們上面所寫的類加載器的結(jié)構(gòu)不同。
大家需要注意雙親委派機(jī)制并不是通過繼承來實(shí)現(xiàn)的,而是相互之間組合而形成的。
所以AppClassLoader沒有繼承自 ExtClassLoader,WebappClassLoader也沒有繼承自AppClassLoader。
至于Common ClassLoader ,Shared ClassLoader,Catalina ClassLoader則是在啟動時初始化的三個不同名字的URLClassLoader。
先來看看Bootstrap#init()方法。init方法會調(diào)用initClassLoaders,同樣也會將Catalina ClassLoader設(shè)置到當(dāng)前線程設(shè)置到當(dāng)前線程,進(jìn)入initClassLoaders來看看。
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 都為空則會返回 commonLoader 類加載器 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方法中。
可以看到這三個基礎(chǔ)類加載器所加載的資源剛好對應(yīng)conf/catalina.properties中的common.loader,server.loader,shared.loader
3.2 層次結(jié)構(gòu)
Common/Catalina/Shared ClassLoader的創(chuàng)建好了之后就會維護(hù)相互之間的組合關(guān)系
其實(shí)也就是設(shè)置了父加載器
3.3 具體的加載過程
源碼比較長,直接進(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中是否加載過此類 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ī)中是否加載過該類 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 // 如果前兩步都沒有找到,則使用系統(tǒng)類加載該類(也就是當(dāng)前JVM的ClassPath)。 // 為了防止覆蓋基礎(chǔ)類實(shí)現(xiàn),這里會判斷class是不是JVMSE中的基礎(chǔ)類庫中類。 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 caughtURL 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-thrownExceptionUtils.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,那么就會完全按照J(rèn)VM的"雙親委托"機(jī)制流程加載類。 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 // 若是沒有委托,則默認(rèn)會首次使用WebappClassLoader來加載類。通過自定義findClass定義處理類加載規(guī)則。 // findClass()會去Web-INF/classes 目錄下查找類。 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, // 那么無條件強(qiáng)制委托給System、Common類加載器去查找該類。 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)用類加載器默認(rèn)的加載順序是:
- (1).先從緩存中加載;
- (2).如果沒有,則從JVM的Bootstrap類加載器加載;
- (3).如果沒有,則從當(dāng)前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序);
- (4).如果沒有,則從父類加載器加載,由于父類加載器采用默認(rèn)的委派模式,所以加載順序是AppClassLoader、Common、Shared。
tomcat提供了delegate屬性用于控制是否啟用java委派模式,默認(rèn)false(不啟用),當(dāng)設(shè)置為true時,tomcat將使用java的默認(rèn)委派模式,這時加載順序如下:
- (1).先從緩存中加載;
- (2).如果沒有,則從JVM的Bootstrap類加載器加載;
- (3).如果沒有,則從父類加載器加載,加載順序是AppClassLoader、Common、Shared。
- (4).如果沒有,則從當(dāng)前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序)
以上就是Tomcat Catalina為什么不new出來原理解析的詳細(xì)內(nèi)容,更多關(guān)于Tomcat Catalina原理的資料請關(guān)注其它相關(guān)文章!
