tomcat類加載器為什么要破壞雙親委派機制?


一、tomcat是個web容器,要解決以下問題

1. 一個web容器可能要部署兩個或者多個應用程序,不同的應用程序,可能會依賴同一個第三方類庫的不同版本,因此要保證每一個應用程序的類庫都是獨立、相互隔離的。

2. 部署在同一個web容器中的相同類庫的相同版本可以共享,否則,會有重復的類庫被加載進JVM

3. web容器也有自己的類庫,不能和應用程序的類庫混淆,需要相互隔離

4. web容器支持jsp文件修改后不用重啟,jsp文件也是要編譯成.class文件的,支持HotSwap功能

 

二、tomcat使用Java默認類加載器的問題

1. 默認的類加載器無法加載兩個相同類庫的不同版本,它只在乎類的全限定類名,並且只有一份,所以無法解決上面1和3,相互隔離的問題

2. 修改jsp文件后,因為類名一樣,默認的類加載器不會重新加載,而是使用方法區中已經存在的類;所以需要每個jsp對應一個唯一的類加載器,當修改jsp的時候,直接卸載唯一的類加載器,然后重新創建類加載器,並加載jsp文件

 

三、tomcat的類加載機制

1. 架構圖

2. tomcat自己定義的類加載器:

CommonClassLoader:tomcat最基本的類加載器,加載路徑中的class可以被tomcat和各個webapp訪問

CatalinaClassLoader:tomcat私有的類加載器,webapp不能訪問其加載路徑下的class,即對webapp不可見

SharedClassLoader:各個webapp共享的類加載器,對tomcat不可見

WebappClassLoader:webapp私有的類加載器,只對當前webapp可見

JspClassLoader

3. 每一個web應用程序對應一個WebappClassLoader,每一個jsp文件對應一個JspClassLoader,所以這兩個類加載器有多個實例

4. 工作原理:

a. CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用,從而實現了公有類庫的共用

b. CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對方相互隔離

c. WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離,多個WebAppClassLoader是同級關系

d. 而JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是為了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並通過再建立一個新的Jsp類加載器來實現JSP文件的HotSwap功能

5. tomcat目錄結構,與上面的類加載器對應

/common/*

/server/*

/shared/*

/WEB-INF/*

6. 默認情況下,conf目錄下的catalina.properties文件,沒有指定server.loader以及shared.loader,所以tomcat沒有建立CatalinaClassLoader和SharedClassLoader的實例,這兩個都會使用CommonClassLoader來代替。Tomcat6之后,把common、shared、server目錄合成了一個lib目錄。所以在我們的服務器里看不到common、shared、server目錄。

 

 

四、tomcat類加載器和雙親委派模型的關系

1. tomcat為了實現隔離性和熱替換,沒有使用默認的類加載器,而是自己實現了類加載器:

每個webappClassLoader加載自己目錄下的class文件

每個jasper類加載器加載一個jsp文件

2. 雙親委派模型的標准是:每個類加載器要加載類的時候,先傳給父類加載器加載,父類加載器加載不了的時候,才由自己加載

3. webappClassLoader和jasperClassLoader沒有傳給父類加載器去加載,還是傳給了父類加載器而父類加載器加載不了?先自己加載

4. 從WebappClassLoader.loadClass源碼上看,確實沒有傳給父類加載器去加載,確實破壞了雙親委派模型,對於一些未加載的非基礎類(非Object,String等),各個web應用自己的類加載器(WebAppClassLoader)會優先加載,加載不到時再交給commonClassLoader走雙親委托

hasExternalRepositories && searchExternalFirst 默認為false

5. 為什么要破壞?不破壞行不行?每個webapp有自己的目錄和類庫,比如一個webapp使用類庫A1.0版本,一個webapp使用類庫A2.0版本,父類加載器加載類庫A1.0版本,如果使用雙親委派,會由commonClassLoader去加載類庫A1.0版本,這樣第二個webapp會有問題

6. 可以通過在Context.xml文件中加上<Loader delegate = "true">開啟正統的“雙親委派”加載機制

public Class<?> findClass(String name) throws ClassNotFoundException {
        // 其他代碼略去.....
       
        // Ask our superclass to locate this class, if possible
        // (throws ClassNotFoundException if it is not found)
        Class<?> clazz = null;
        try {
            if (log.isTraceEnabled())
                log.trace("      findClassInternal(" + name + ")");
            //        (1)默認為false
            if (hasExternalRepositories && searchExternalFirst) {
                try {
                    clazz = super.findClass(name);
                } catch(ClassNotFoundException cnfe) {
                    // Ignore - will search internal repositories next
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoaderBase.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            //            (2)
            if ((clazz == null)) {
                try {
                    clazz = findClassInternal(name);
                } catch(ClassNotFoundException cnfe) {
                    if (!hasExternalRepositories || searchExternalFirst) {
                        throw cnfe;
                    }
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoaderBase.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            
      //其他代碼略去........
        return (clazz);
 
    }

 

 加載過程:

  1. 先在本地緩存中查找是否已經加載過該類(對於一些已經加載了的類,會被緩存在resourceEntries這個數據結構中),如果已經加載即返回,否則 繼續下一步。
  2. 讓系統類加載器(AppClassLoader)嘗試加載該類,主要是為了防止一些基礎類會被web中的類覆蓋,如果加載到即返回,返回繼續。
  3. 前兩步均沒加載到目標類,那么web應用的類加載器將自行加載,如果加載到則返回,否則繼續下一步。
  4. 最后還是加載不到的話,則委托父類加載器(Common ClassLoader)去加載。

 

 

五、其他破壞了雙親委派模型的技術

1. OSGI是基於Java語言的動態模塊化規范,類加載器之間是網狀結構,更加靈活,但是也更復雜

2. JNDI服務,使用線程上線文類加載器,父類加載器去使用子類加載器

 

 

 

 

參考文檔:

https://blog.csdn.net/qq_38182963/article/details/78660779

https://blog.csdn.net/moakun/article/details/80563505

https://www.cnblogs.com/aspirant/p/8991830.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM