欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

深入探討 Java 類加載器

系統 2062 0

類加載器是 Java 語言的一個創新,也是 Java 語言流行的重要原因之一。它使得 Java 類可以被動態加載到 Java 虛擬機中并執行。類加載器從 JDK 1.0 就出現了,最初是為了滿足 Java Applet 的需要而開發出來的。Java Applet 需要從遠程下載 Java 類文件到瀏覽器中并執行。現在類加載器在 Web 容器和 OSGi 中得到了廣泛的使用。一般來說,Java 應用的開發人員不需要直接同類加載器進行交互。Java 虛擬機默認的行為就已經足夠滿足大多數情況的需求了。不過如果遇到了需要與類加載器進行交互的情況,而對類加載器的機制又不是很了解的話,就很容易花大量的時間去調試 ClassNotFoundException NoClassDefFoundError 等異常。本文將詳細介紹 Java 的類加載器,幫助讀者深刻理解 Java 語言中的這個重要概念。下面首先介紹一些相關的基本概念。

類加載器基本概念

顧名思義,類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之后就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,并轉換成 java.lang.Class 類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance() 方法就可以創建出該類的一個對象。實際的情況可能更加復雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。

基本上所有的類加載器都是 java.lang.ClassLoader 類的一個實例。下面詳細介紹這個 Java 類。

java.lang.ClassLoader 類介紹

java.lang.ClassLoader 類的基本職責就是根據一個指定的類的名稱,找到或者生成其對應的字節代碼,然后從這些字節代碼中定義出一個 Java 類,即 java.lang.Class 類的一個實例。除此之外, ClassLoader 還負責加載 Java 應用所需的資源,如圖像文件和配置文件等。不過本文只討論其加載類的功能。為了完成加載類的這個職責, ClassLoader 提供了一系列的方法,比較重要的方法如 表 1 所示。關于這些方法的細節會在下面進行介紹。


表 1. ClassLoader 中與加載類相關的方法

方法 說明
getParent() 返回該類加載器的父類加載器。
loadClass(String name) 加載名稱為 name 的類,返回的結果是 java.lang.Class 類的實例。
findClass(String name) 查找名稱為 name 的類,返回的結果是 java.lang.Class 類的實例。
findLoadedClass(String name) 查找名稱為 name 的已經被加載過的類,返回的結果是 java.lang.Class 類的實例。
defineClass(String name, byte[] b, int off, int len) 把字節數組 b 中的內容轉換成 Java 類,返回的結果是 java.lang.Class 類的實例。這個方法被聲明為 final 的。
resolveClass(Class<?> c)

鏈接指定的 Java 類。

對于 表 1 中給出的方法,表示類名稱的 name 參數的值是類的二進制名稱。需要注意的是內部類的表示,如 com.example.Sample$1 com.example.Sample$Inner 等表示方式。這些方法會在下面介紹類加載器的工作機制時,做進一步的說明。下面介紹類加載器的樹狀組織結構。

類加載器的樹狀組織結構

Java 中的類加載器大致可以分成兩類,一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。系統提供的類加載器主要有下面三個:

  • 引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實現的,并不繼承自 java.lang.ClassLoader 。
  • 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
  • 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader() 來獲取它。

除了系統提供的類加載器以外,開發人員可以通過繼承 java.lang.ClassLoader 類的方式實現自己的類加載器,以滿足一些特殊的需求。

除了引導類加載器之外,所有的類加載器都有一個父類加載器。通過 表 1 中給出的 getParent() 方法可以得到。對于系統提供的類加載器來說,系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器;對于開發人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器。因為類加載器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的。一般來說,開發人員編寫的類加載器的父類加載器是系統類加載器。類加載器通過這種方式組織起來,形成樹狀結構。樹的根節點就是引導類加載器。 圖 1 中給出了一個典型的類加載器樹狀組織結構示意圖,其中的箭頭指向的是父類加載器。


圖 1. 類加載器樹狀組織結構示意圖
類加載器樹狀組織結構示意圖

代碼清單 1 演示了類加載器的樹狀組織結構。


清單 1. 演示類加載器的樹狀組織結構

            public class ClassLoaderTree { 

    public static void main(String[] args) { 
        ClassLoader loader = ClassLoaderTree.class.getClassLoader(); 
        while (loader != null) { 
            System.out.println(loader.toString()); 
            loader = loader.getParent(); 
        } 
    } 
}
          

每個 Java 類都維護著一個指向定義它的類加載器的引用,通過 getClassLoader() 方法就可以獲取到此引用。 代碼清單 1 中通過遞歸調用 getParent() 方法來輸出全部的父類加載器。 代碼清單 1 的運行結果如 代碼清單 2 所示。


清單 2. 演示類加載器的樹狀組織結構的運行結果

            sun.misc.Launcher$AppClassLoader@9304b1 
sun.misc.Launcher$ExtClassLoader@190d11
          

代碼清單 2 所示,第一個輸出的是 ClassLoaderTree 類的類加載器,即系統類加載器。它是 sun.misc.Launcher$AppClassLoader 類的實例;第二個輸出的是擴展類加載器,是 sun.misc.Launcher$ExtClassLoader 類的實例。需要注意的是這里并沒有輸出引導類加載器,這是由于有些 JDK 的實現對于父類加載器是引導類加載器的情況, getParent() 方法返回 null

在了解了類加載器的樹狀組織結構之后,下面介紹類加載器的代理模式。

類加載器的代理模式

類加載器在嘗試自己去查找某個類的字節代碼并定義它時,會先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推。在介紹代理模式背后的動機之前,首先需要說明一下 Java 虛擬機是如何判定兩個 Java 類是相同的。Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認為兩個類是相同的。即便是同樣的字節代碼,被不同的類加載器加載之后所得到的類,也是不同的。比如一個 Java 類 com.example.Sample ,編譯之后生成了字節代碼文件 Sample.class 。兩個不同的類加載器 ClassLoaderA ClassLoaderB 分別讀取了這個 Sample.class 文件,并定義出兩個 java.lang.Class 類的實例來表示這個類。這兩個實例是不相同的。對于 Java 虛擬機來說,它們是不同的類。試圖對這兩個類的對象進行相互賦值,會拋出運行時異常 ClassCastException 。下面通過示例來具體說明。 代碼清單 3 中給出了 Java 類 com.example.Sample 。


清單 3. com.example.Sample 類

            package com.example; 

public class Sample { 
    private Sample instance; 

    public void setSample(Object instance) { 
        this.instance = (Sample) instance; 
    } 
}
          

代碼清單 3 所示, com.example.Sample 類的方法 setSample 接受一個 java.lang.Object 類型的參數,并且會把該參數強制轉換成 com.example.Sample 類型。測試 Java 類是否相同的代碼如 代碼清單 4 所示。


清單 4. 測試 Java 類是否相同

            public void testClassIdentity() { 
    String classDataRootPath = "C://workspace//Classloader//classData"; 
    FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); 
    FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); 
    String className = "com.example.Sample"; 	
    try { 
        Class<?> class1 = fscl1.loadClass(className); 
        Object obj1 = class1.newInstance(); 
        Class<?> class2 = fscl2.loadClass(className); 
        Object obj2 = class2.newInstance(); 
        Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class); 
        setSampleMethod.invoke(obj1, obj2); 
    } catch (Exception e) { 
        e.printStackTrace(); 
    } 
}
          

代碼清單 4 中使用了類 FileSystemClassLoader 的兩個不同實例來分別加載類 com.example.Sample ,得到了兩個不同的 java.lang.Class 的實例,接著通過 newInstance() 方法分別生成了兩個類的對象 obj1 obj2 ,最后通過 Java 的反射 API 在對象 obj1 上調用方法 setSample ,試圖把對象 obj2 賦值給 obj1 內部的 instance 對象。 代碼清單 4 的運行結果如 代碼清單 5 所示。


清單 5. 測試 Java 類是否相同的運行結果

            java.lang.reflect.InvocationTargetException 
	 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
	 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
	 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
	 at java.lang.reflect.Method.invoke(Method.java:597) 
	 at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26) 
	 at classloader.ClassIdentity.main(ClassIdentity.java:9) 
Caused by: java.lang.ClassCastException: com.example.Sample 
    cannot be cast to com.example.Sample 
	 at com.example.Sample.setSample(Sample.java:7) 
	 ... 6 more 

          

代碼清單 5 給出的運行結果可以看到,運行時拋出了 java.lang.ClassCastException 異常。雖然兩個對象 obj1 obj2 的類的名字相同,但是這兩個類是由不同的類加載器實例來加載的,因此不被 Java 虛擬機認為是相同的。

了解了這一點之后,就可以理解代理模式的設計動機了。代理模式是為了保證 Java 核心庫的類型安全。所有 Java 應用都至少需要引用 java.lang.Object 類,也就是說在運行的時候, java.lang.Object 這個類需要被加載到 Java 虛擬機中。如果這個加載過程由 Java 應用自己的類加載器來完成的話,很可能就存在多個版本的 java.lang.Object 類,而且這些類之間是不兼容的。通過代理模式,對于 Java 核心庫的類的加載工作由引導類加載器來統一完成,保證了 Java 應用所使用的都是同一個版本的 Java 核心庫的類,是互相兼容的。

不同的類加載器為相同名稱的類創建了額外的名稱空間。相同名稱的類可以并存在 Java 虛擬機中,只需要用不同的類加載器來加載它們即可。不同類加載器加載的類之間是不兼容的,這就相當于在 Java 虛擬機內部創建了一個個相互隔離的 Java 類空間。這種技術在許多框架中都被用到,后面會詳細介紹。

下面具體介紹類加載器加載類的詳細過程。

加載類的過程

在前面介紹類加載器的代理模式的時候,提到過類加載器會首先代理給其它類加載器來嘗試加載某個類。這就意味著真正完成類的加載工作的類加載器和啟動這個加載過程的類加載器,有可能不是同一個。真正完成類的加載工作是通過調用 defineClass 來實現的;而啟動類的加載過程是通過調用 loadClass 來實現的。前者稱為一個類的定義加載器(defining loader),后者稱為初始加載器(initiating loader)。在 Java 虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。也就是說,哪個類加載器啟動類的加載過程并不重要,重要的是最終定義這個類的加載器。兩種類加載器的關聯之處在于:一個類的定義加載器是它引用的其它類的初始加載器。如類 com.example.Outer 引用了類 com.example.Inner ,則由類 com.example.Outer 的定義加載器負責啟動類 com.example.Inner 的加載過程。

方法 loadClass() 拋出的是 java.lang.ClassNotFoundException 異常;方法 defineClass() 拋出的是 java.lang.NoClassDefFoundError 異常。

類加載器在成功加載某個類之后,會把得到的 java.lang.Class 類的實例緩存起來。下次再請求加載該類的時候,類加載器會直接使用緩存的類的實例,而不會嘗試再次加載。也就是說,對于一個類加載器實例來說,相同全名的類只加載一次,即 loadClass 方法不會被重復調用。

下面討論另外一種類加載器:線程上下文類加載器。

線程上下文類加載器

線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread 中的方法 getContextClassLoader() setContextClassLoader(ClassLoader cl) 用來獲取和設置線程的上下文類加載器。如果沒有通過 setContextClassLoader(ClassLoader cl) 方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼可以通過此類加載器來加載類和資源。

前面提到的類加載器的代理模式并不能解決 Java 應用開發中會遇到的類加載器的全部問題。Java 提供了很多服務提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫來提供,如 JAXP 的 SPI 接口定義包含在 javax.xml.parsers 包中。這些 SPI 的實現代碼很可能是作為 Java 應用所依賴的 jar 包被包含進來,可以通過類路徑(CLASSPATH)來找到,如實現了 JAXP SPI 的 Apache Xerces 所包含的 jar 包。SPI 接口中的代碼經常需要加載具體的實現類。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory 類中的 newInstance() 方法用來生成一個新的 DocumentBuilderFactory 的實例。這里的實例的真正的類是繼承自 javax.xml.parsers.DocumentBuilderFactory ,由 SPI 的實現所提供的。如在 Apache Xerces 中,實現的類是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl 。而問題在于,SPI 的接口是 Java 核心庫的一部分,是由引導類加載器來加載的;SPI 實現的 Java 類一般是由系統類加載器來加載的。引導類加載器是無法找到 SPI 的實現類的,因為它只加載 Java 的核心庫。它也不能代理給系統類加載器,因為它是系統類加載器的祖先類加載器。也就是說,類加載器的代理模式無法解決這個問題。

線程上下文類加載器正好解決了這個問題。如果不做任何的設置,Java 應用的線程的上下文類加載器默認就是系統上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就可以成功的加載到 SPI 實現的類。線程上下文類加載器在很多 SPI 的實現中都會用到。

下面介紹另外一種加載類的方法: Class.forName 。

Class.forName

Class.forName 是一個靜態方法,同樣可以用來加載類。該方法有兩種形式: Class.forName(String name, boolean initialize, ClassLoader loader) Class.forName(String className) 。第一種形式的參數 name 表示的是類的全名; initialize 表示是否初始化類; loader 表示加載時使用的類加載器。第二種形式則相當于設置了參數 initialize 的值為 true , loader 的值為當前類的類加載器。 Class.forName 的一個很常見的用法是在加載數據庫驅動的時候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() 用來加載 Apache Derby 數據庫的驅動。

在介紹完類加載器相關的基本概念之后,下面介紹如何開發自己的類加載器。


開發自己的類加載器

雖然在絕大多數情況下,系統默認提供的類加載器實現已經可以滿足需求。但是在某些情況下,您還是需要為應用開發出自己的類加載器。比如您的應用通過網絡來傳輸 Java 類的字節代碼,為了保證安全性,這些字節代碼經過了加密處理。這個時候您就需要自己的類加載器來從某個網絡地址上讀取加密后的字節代碼,接著進行解密和驗證,最后定義出要在 Java 虛擬機中運行的類來。下面將通過兩個具體的實例來說明類加載器的開發。

文件系統類加載器

第一個類加載器用來加載存儲在文件系統上的 Java 字節代碼。完整的實現如 代碼清單 6 所示。


清單 6. 文件系統類加載器

            public class FileSystemClassLoader extends ClassLoader { 

    private String rootDir; 

    public FileSystemClassLoader(String rootDir) { 
        this.rootDir = rootDir; 
    } 

    protected Class<?> findClass(String name) throws ClassNotFoundException { 
        byte[] classData = getClassData(name); 
        if (classData == null) { 
            throw new ClassNotFoundException(); 
        } 
        else { 
            return defineClass(name, classData, 0, classData.length); 
        } 
    } 

    private byte[] getClassData(String className) { 
        String path = classNameToPath(className); 
        try { 
            InputStream ins = new FileInputStream(path); 
            ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
            int bufferSize = 4096; 
            byte[] buffer = new byte[bufferSize]; 
            int bytesNumRead = 0; 
            while ((bytesNumRead = ins.read(buffer)) != -1) { 
                baos.write(buffer, 0, bytesNumRead); 
            } 
            return baos.toByteArray(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        return null; 
    } 

    private String classNameToPath(String className) { 
        return rootDir + File.separatorChar 
                + className.replace('.', File.separatorChar) + ".class"; 
    } 
}
          

代碼清單 6 所示,類 FileSystemClassLoader 繼承自類 java.lang.ClassLoader 。在 表 1 中列出的 java.lang.ClassLoader 類的常用方法中,一般來說,自己開發的類加載器只需要覆寫 findClass(String name) 方法即可。 java.lang.ClassLoader 類的方法 loadClass() 封裝了前面提到的代理模式的實現。該方法會首先調用 findLoadedClass() 方法來檢查該類是否已經被加載過;如果沒有加載過的話,會調用父類加載器的 loadClass() 方法來嘗試加載該類;如果父類加載器無法加載該類的話,就調用 findClass() 方法來查找該類。因此,為了保證類加載器都正確實現代理模式,在開發自己的類加載器時,最好不要覆寫 loadClass() 方法,而是覆寫 findClass() 方法。

FileSystemClassLoader findClass() 方法首先根據類的全名在硬盤上查找類的字節代碼文件(.class 文件),然后讀取該文件內容,最后通過 defineClass() 方法來把這些字節代碼轉換成 java.lang.Class 類的實例。

網絡類加載器

下面將通過一個網絡類加載器來說明如何通過類加載器來實現組件的動態更新。即基本的場景是:Java 字節代碼(.class)文件存放在服務器上,客戶端通過網絡的方式獲取字節代碼并執行。當有版本更新的時候,只需要替換掉服務器上保存的文件即可。通過類加載器可以比較簡單的實現這種需求。

NetworkClassLoader 負責通過網絡下載 Java 類字節代碼并定義出 Java 類。它的實現與 FileSystemClassLoader 類似。在通過 NetworkClassLoader 加載了某個版本的類之后,一般有兩種做法來使用它。第一種做法是使用 Java 反射 API。另外一種做法是使用接口。需要注意的是,并不能直接在客戶端代碼中引用從服務器上下載的類,因為客戶端代碼的類加載器找不到這些類。使用 Java 反射 API 可以直接調用 Java 類的方法。而使用接口的做法則是把接口的類放在客戶端中,從服務器上加載實現此接口的不同版本的類。在客戶端通過相同的接口來使用這些實現類。網絡類加載器的具體代碼見 下載

在介紹完如何開發自己的類加載器之后,下面說明類加載器和 Web 容器的關系。


類加載器與 Web 容器

對于運行在 Java EE? 容器中的 Web 應用來說,類加載器的實現方式與一般的 Java 應用有所不同。不同的 Web 容器的實現方式也會有所不同。以 Apache Tomcat 來說,每個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不同的是它是首先嘗試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。這是 Java Servlet 規范中的推薦做法,其目的是使得 Web 應用自己的類的優先級高于 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找范圍之內的。這也是為了保證 Java 核心庫的類型安全。

絕大多數情況下,Web 應用的開發人員不需要考慮與類加載器相關的細節。下面給出幾條簡單的原則:

  • 每個 Web 應用自己的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes WEB-INF/lib 目錄下面。
  • 多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由所有 Web 應用共享的目錄下面。
  • 當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。

在介紹完類加載器與 Web 容器的關系之后,下面介紹它與 OSGi 的關系。


類加載器與 OSGi

OSGi? 是 Java 上的動態模塊系統。它為開發人員提供了面向服務和基于組件的運行環境,并提供標準的方式用來管理軟件的生命周期。OSGi 已經被實現和部署在很多產品上,在開源社區也得到了廣泛的支持。Eclipse 就是基于 OSGi 技術來構建的。

OSGi 中的每個模塊(bundle)都包含 Java 包和類。模塊可以聲明它所依賴的需要導入(import)的其它模塊的 Java 包和類(通過 Import-Package ),也可以聲明導出(export)自己的包和類,供其它模塊使用(通過 Export-Package )。也就是說需要能夠隱藏和共享一個模塊中的某些 Java 包和類。這是通過 OSGi 特有的類加載器機制來實現的。OSGi 中的每個模塊都有對應的一個類加載器。它負責加載模塊自己包含的 Java 包和類。當它需要加載 Java 核心庫的類時(以 java 開頭的包和類),它會代理給父類加載器(通常是啟動類加載器)來完成。當它需要加載所導入的 Java 類時,它會代理給導出此 Java 類的模塊來完成加載。模塊也可以顯式的聲明某些 Java 包和類,必須由父類加載器來加載。只需要設置系統屬性 org.osgi.framework.bootdelegation 的值即可。

假設有兩個模塊 bundleA 和 bundleB,它們都有自己對應的類加載器 classLoaderA 和 classLoaderB。在 bundleA 中包含類 com.bundleA.Sample ,并且該類被聲明為導出的,也就是說可以被其它模塊所使用的。bundleB 聲明了導入 bundleA 提供的類 com.bundleA.Sample ,并包含一個類 com.bundleB.NewSample 繼承自 com.bundleA.Sample 。在 bundleB 啟動的時候,其類加載器 classLoaderB 需要加載類 com.bundleB.NewSample ,進而需要加載類 com.bundleA.Sample 。由于 bundleB 聲明了類 com.bundleA.Sample 是導入的,classLoaderB 把加載類 com.bundleA.Sample 的工作代理給導出該類的 bundleA 的類加載器 classLoaderA。classLoaderA 在其模塊內部查找類 com.bundleA.Sample 并定義它,所得到的類 com.bundleA.Sample 實例就可以被所有聲明導入了此類的模塊使用。對于以 java 開頭的類,都是由父類加載器來加載的。如果聲明了系統屬性 org.osgi.framework.bootdelegation=com.example.core.* ,那么對于包 com.example.core 中的類,都是由父類加載器來完成的。

OSGi 模塊的這種類加載器結構,使得一個類的不同版本可以共存在 Java 虛擬機中,帶來了很大的靈活性。不過它的這種不同,也會給開發人員帶來一些麻煩,尤其當模塊需要使用第三方提供的庫的時候。下面提供幾條比較好的建議:

  • 如果一個類庫只有一個模塊使用,把該類庫的 jar 包放在模塊中,在 Bundle-ClassPath 中指明即可。
  • 如果一個類庫被多個模塊共用,可以為這個類庫單獨的創建一個模塊,把其它模塊需要用到的 Java 包聲明為導出的。其它模塊聲明導入這些類。
  • 如果類庫提供了 SPI 接口,并且利用線程上下文類加載器來加載 SPI 實現的 Java 類,有可能會找不到 Java 類。如果出現了 NoClassDefFoundError 異常,首先檢查當前線程的上下文類加載器是否正確。通過 Thread.currentThread().getContextClassLoader() 就可以得到該類加載器。該類加載器應該是該模塊對應的類加載器。如果不是的話,可以首先通過 class.getClassLoader() 來得到模塊對應的類加載器,再通過 Thread.currentThread().setContextClassLoader() 來設置當前線程的上下文類加載器。


總結

類加載器是 Java 語言的一個創新。它使得動態安裝和更新軟件組件成為可能。本文詳細介紹了類加載器的相關話題,包括基本概念、代理模式、線程上下文類加載器、與 Web 容器和 OSGi 的關系等。開發人員在遇到 ClassNotFoundException NoClassDefFoundError 等異常的時候,應該檢查拋出異常的類的類加載器和當前線程的上下文類加載器,從中可以發現問題的所在。在開發自己的類加載器的時候,需要注意與已有的類加載器組織結構的協調。

深入探討 Java 類加載器


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 久久久成人精品 | 中文字幕一区二区在线观看 | 久草中文网 | 国产在线激情视频 | 插下面视频| 欧美一区二区三区四区夜夜大片 | 日韩卡1卡2 卡三卡2021老狼 | 欧美成人h版整片合集 | 国产AV亚洲精品久久久久 | 日本在线视频www鲁啊鲁 | 欧美综合成人 | 天天草夜夜操 | 韩日精品视频 | 欧美日本一区视频免费 | 奇米影视 首页 | 97久久精品人人做人人爽50路 | 天天操天天射天天爽 | 性xxxx免费观看视频 | 一级毛片a级 | 国产美女亚洲精品久久久综合 | 国产成人在线一区二区 | 欧洲精品视频完整版在线 | 狠狠干奇米 | 国产成人综合在线 | 欧美成人欧美激情欧美风情 | 黄色免费在线观看 | 91短视频网址| 日韩av日韩 | av免费观看网站 | 亚洲午夜精品一区二区三区他趣 | 欧美影院推理片免费看 | 国产亚洲综合一区在线 | 日韩欧美精品在线观看 | 国产视频一区二区 | 午夜精品毛片 | 国产女人与拘做受视频 | 国产成人自拍一区 | 日韩欧美在线视频 | 色福利网| 99草在线视频 | 国产乱人乱精一区二区视频密 |