Tomcat - 深度學習 - 類加器詳解

前言

Tomcat如何實現不同的應用程序,使用不同的第三方類庫?帶着疑問學下去

打破雙親委派

以Tomcat類加載爲例,Tomcat 如果使用默認的雙親委派類加載機制行不行? 我們思考一下:

Tomcat是個web容器, 那麼它要解決什麼問題:

  1. 一個web容器可能需要部署兩個應用程序,不同的應用程序可能會依賴同一個第三方類庫的不同版本,不能要求同一個類庫在同一個服務器只有一份,因此要保證每個應用程序的 類庫都是獨立的,保證相互隔離。
  2. 部署在同一個web容器中相同的類庫相同的版本可以共享。否則,如果服務器有10個應用程序,那麼要有10份相同的類庫加載進虛擬機。
  3. web容器也有自己依賴的類庫,不能與應用程序的類庫混淆。基於安全考慮,應該讓容器的類庫和程序的類庫隔離開來。
  4. web容器要支持jsp的修改,我們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中運行,但程序運行後修改jsp已經是司空見慣的事情, web容器需要支持 jsp 修改後不 用重啓。

比如以下僞代碼,我們又要確保各自隔離,又要確保底層庫通用。必定主要實現自定義ClassLoader

Tomcat 平臺
  應用A
  	com.xm.Test1
  應用B
  	com.xm.Test1

Tomcat 如果使用默認的雙親委派類加載機制行不行?

答案是不行的。爲什麼?

第一個問題,如果使用默認的類加載器機制,那麼是無法加載兩個相同類庫的不同版本的, 默認的類加器是不管你是什麼版本的,只在乎你的全限定類名,並且只有一份。

第二個問 題,默認的類加載器是能夠實現的,因爲他的職責就是保證唯一性。 第三個問題和第一個問題一樣。 我們再看第四個問題,我們想我們要怎麼實現jsp文件的熱加載,jsp 文件其實也就是class 文件,那麼如果修改了,但類名還是一樣,類加載器會直接取方法區中已經存在的,修改後 的jsp是不會重新加載的。那麼怎麼辦呢?我們可以直接卸載掉這jsp文件的類加載器,所以 你應該想到了,每個jsp文件對應一個唯一的類加載器,當一個jsp文件修改了,就直接卸載 這個jsp類加載器。重新創建類加載器,重新加載jsp文件。

Tomcat自定義加載器詳解

在這裏插入圖片描述

tomcat的幾個主要類加載器:

  • commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;
  • catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
  • sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於所有Webapp可見,但是對於Tomcat容器不可見;
  • WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見;主要負責管理用戶自定義目錄下的 lib,classes

從圖中的委派關係中可以看出:

CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader使 用,從而實現了公有類庫的共用,而CatalinaClassLoader和SharedClassLoader自己能加載的類則與對方相互隔離。

WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。 而JasperLoader的加載範圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的 目的就是爲了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的 JasperLoader的實例,並通過再建立一個新的Jsp類加載器來實現JSP文件的熱加載功能。

tomcat 這種類加載機制違背了java 推薦的雙親委派模型了嗎?

答案是:違背了。 我們前面說過,雙親委派機制要求除了頂層的啓動類加載器之外,其餘的類加載器都應當由自己的父類加載器加載。很顯然,tomcat 不是這樣實現,tomcat 爲了實現隔離性,沒有遵守這個約定, 每個 webappClassLoader加載自己的目錄下的class文件,不會傳遞給父類加載器,打破了雙親委派機制。

public class JarFileTest {
    public static void main(String[] args) {
        JarFile jarFile = new JarFile("xxx.jar");
        JarEntry entry = jarFile.getJarEntry("java/servlet/AsyncEvent1.class");
        System.out.println(entry.getName());
    }
}

加載規則

1. map -> 自己緩存中查找
2. jvm -> 查找是否已經加載
3. 嘗試通過類加載器(ExtClassLoader)加載類,防止Webapp重寫JDK中的類
4. Tomcat認定特殊的類,是否委派給父類去加載 CommonClassLoader(URLClassLoader)去加載
5. 去自己的webapps裏面去加載
6. 如果還沒有加載到,無條件交給父類去加載 CommonClassLoader(URLClassLoader)去加載

熱加載與熱部署

熱加載—是針對應用Context來的

  • 熱加載的目的是讓JVM中老的class對象替換爲新的class對象,因爲不能直接替換,我們需要卸載掉classLoader

  • 如何開啓熱加載 reloadable 默認false

    <Context path="/hello" docBase="\ServletDemo" reloadable="true"/>
    
  • 刪除修改,都會進行熱加載

  • 添加不會熱加載

    public class WebappLoader extends LifecycleMBeanBase
        implements Loader, PropertyChangeListener {
      
        @Override
        public void backgroundProcess() {
            if (reloadable && modified()) {
                // 觸發了熱加載
                try {
                    Thread.currentThread().setContextClassLoader
                        (WebappLoader.class.getClassLoader());
                    if (context != null) {
                        context.reload();
                    }
                } finally {
                    if (context != null && context.getLoader() != null) {
                        Thread.currentThread().setContextClassLoader
                            (context.getLoader().getClassLoader());
                    }
                }
            }
        }
    }
    

    modified 最終調用這裏

    public abstract class WebappClassLoaderBase extends ... {
     	public boolean modified() {
            for (Entry<String,ResourceEntry> entry : resourceEntries.entrySet()) {
                long cachedLastModified = entry.getValue().lastModified;
                long lastModified = resources.getClassLoaderResource(
                        entry.getKey()).getLastModified();
                // 某一個文件最後一次修改時間是什麼
                if (lastModified != cachedLastModified) {
                  ...
                    return true;
                }
            }
    
            // Check if JARs have been added or removed
            // 檢測jar包刪除與修改
            WebResource[] jars = resources.listResources("/WEB-INF/lib");
            // Filter out non-JAR resources
    				...
            // No classes have been modified
            return false;
        }
    }
    

    reload源碼流程

    public class StandardContext extends ContainerBase
            implements Context, NotificationEmitter {
    
    		@Override
        public synchronized void reload() {
          	// 暫停,停止接受請求等
            setPaused(true);
           // 停止,老webappClassLoader=null
            stop();
           // 開始,new webappClassLoader()類加載器,通過它重新加載新的類
            start();
            // 恢復
            setPaused(false);
    
        }
    }
    

熱部署—Host (如果我們某個虛擬主機應用發生變化,進行熱部署)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章