1. 程式人生 > >JDBC、Tomcat為什麼要破壞雙親委派模型?

JDBC、Tomcat為什麼要破壞雙親委派模型?

問題一:雙親委派模型是什麼

如果一個類載入器收到了載入某個類的請求,則該類載入器並不會去載入該類,而是把這個請求委派給父類載入器,每一個層次的類載入器都是如此,因此所有的類載入請求最終都會傳送到頂端的啟動類載入器;只有當父類載入器在其搜尋範圍內無法找到所需的類,並將該結果反饋給子類載入器,子類載入器會嘗試去自己載入。

雙親委派模型的好處

這樣做的好處就是:Java類隨著它的類載入器一起具備了一種帶有優先順序的層次關係。例如類java.lang.Object,它存放在rt.jar中,無論哪一個類載入器要載入這個類,最終都是委派給處於模型最頂端的啟動類載入器進行載入,因此Object類在程式的各種類載入器環境中都是同一個類。相反,如果沒有使用雙親委派模型,由各個類載入器自行去載入的話,如果使用者自己編寫了一個稱為java.lang.object的類,並放在程式的ClassPath中,那系統中將會出現多個不同的Object類,Java型別體系中最基礎的行為也就無法保證,應用程式也將會變得一片混亂。

其次是考慮到安全因素。假設通過網路傳遞一個名為java.lang.Integer的類,通過雙親委託模式傳遞到啟動類載入器,而啟動類載入器在核心Java API發現這個名字的類,發現該類已被載入,並不會重新載入網路傳遞的過來的java.lang.Integer,而直接返回已載入過的Integer.class,這樣便可以防止核心API庫被隨意篡改。

問題二:JDBC為什麼要破壞雙親委派模型

問題背景

在JDBC 4.0之後實際上我們不需要再呼叫Class.forName來載入驅動程式了,我們只需要把驅動的jar包放到工程的類載入路徑裡,那麼驅動就會被自動載入。

這個自動載入採用的技術叫做SPI,資料庫驅動廠商也都做了更新。可以看一下jar包裡面的META-INF/services目錄,裡面有一個java.sql.Driver的檔案,檔案裡面包含了驅動的全路徑名。

使用上,我們只需要通過下面一句就可以建立資料庫的連線:

Connection con =    
             DriverManager.getConnection(url , username , password ) ;   

問題解答

因為類載入器受到載入範圍的限制,在某些情況下父類載入器無法載入到需要的檔案,這時候就需要委託子類載入器去載入class檔案。

JDBC的Driver介面定義在JDK中,其實現由各個資料庫的服務商來提供,比如MySQL驅動包。DriverManager 類中要載入各個實現了Driver介面的類,然後進行管理,但是DriverManager位於 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap類載入器載入,而其Driver介面的實現類是位於服務商提供的 Jar 包,根據類載入機制,當被裝載的類引用了另外一個類的時候,虛擬機器就會使用裝載第一個類的類裝載器裝載被引用的類。也就是說BootStrap類載入器還要去載入jar包中的Driver介面的實現類。我們知道,BootStrap類載入器預設只負責載入 $JAVA_HOME中jre/lib/rt.jar 裡所有的class,所以需要由子類載入器去載入Driver實現,這就破壞了雙親委派模型。

檢視DriverManager類的原始碼,看到在使用DriverManager的時候會觸發其靜態程式碼塊,呼叫 loadInitialDrivers() 方法,並呼叫ServiceLoader.load(Driver.class) 載入所有在META-INF/services/java.sql.Driver 檔案裡邊的類到JVM記憶體,完成驅動的自動載入。

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

    }
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

這個子類載入器是通過 Thread.currentThread().getContextClassLoader() 得到的執行緒上下文載入器。

那麼這個上下文類載入器又是什麼呢?

public Launcher() {
    ...
    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    Thread.currentThread().setContextClassLoader(this.loader);
    ...
}

可以看到,在 sun.misc.Launcher 初始化的時候,會獲取AppClassLoader,然後將其設定為上下文類載入器,所以執行緒上下文類載入器預設情況下就是系統載入器。

問題三:Tomcat為什麼要破壞雙親委派模型

Tomcat類載入器:

Tomcat如何破壞雙親委派模型的呢?

每個Tomcat的webappClassLoader載入自己的目錄下的class檔案,不會傳遞給父類載入器。

事實上,tomcat之所以造了一堆自己的classloader,大致是出於下面三類目的:

  • 對於各個 webapp中的 classlib,需要相互隔離,不能出現一個應用中載入的類庫會影響另一個應用的情況,而對於許多應用,需要有共享的lib以便不浪費資源。
  • jvm一樣的安全性問題。使用單獨的 classloader去裝載 tomcat自身的類庫,以免其他惡意或無意的破壞;
  • 熱部署。相信大家一定為 tomcat修改檔案不用重啟就自動重新裝載類庫而驚歎吧。

延伸閱讀

https://blog.csdn.net/u012129558/article/details/81540804

https://blog.csdn.net/liweisnake/article/details/8470