大战熟女丰满人妻av-荡女精品导航-岛国aaaa级午夜福利片-岛国av动作片在线观看-岛国av无码免费无禁网站-岛国大片激情做爰视频

專注Java教育14年 全國咨詢/投訴熱線:400-8080-105
動力節點LOGO圖
始于2009,口口相傳的Java黃埔軍校
首頁 常見問題 深入探討 Java 類加載器,你了解多少呢

深入探討 Java 類加載器,你了解多少呢

更新時間:2019-08-03 09:00:00 來源:動力節點 瀏覽1844次

  類加載器(class loader)是 Java™中的一個很重要的概念。類加載器負責加載 Java 類的字節代碼到 Java 虛擬機中。本文首先詳細介紹了 Java 類加載器的基本概念,包括代理模式、加載類的具體過程和線程上下文類加載器等,接著介紹如何開發自己的類加載器,最后介紹了類加載器在 Web 容器和 OSGi™中的應用。

  在 IBM Bluemix 云平臺上開發并部署您的下一個應用。

  66.png

  開始您的試用

  類加載器是 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 類。通過此實例的 ewInstance()方法就可以創建出該類的一個對象。實際的情況可能更加復雜,比如 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. 類加載器樹狀組織結構示意圖

  classload_tree.png

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

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

  public class ClassLoaderTree {public static void main(String[] args) { ClassLoader loader = ClassLoaderTree.class.getClassLoader(); while (loader != ull) { 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:workspaceClassloaderclassData";

  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類中的 ewInstance()方法用來生成一個新的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)。第一種形式的參數 ame表示的是類的全名;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 == ull) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = ew 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 ame)方法即可。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 包聲明為導出的。其它模塊聲明導入這些類。

深入探討 Java 類加載器,你了解多少呢

  如果類庫提供了 SPI 接口,并且利用線程上下文類加載器來加載 SPI 實現的 Java 類,有可能會找不到 Java 類。如果出現了NoClassDefFoundError異常,首先檢查當前線程的上下文類加載器是否正確。通過Thread.currentThread().getContextClassLoader()就可以得到該類加載器。該類加載器應該是該模塊對應的類加載器。如果不是的話,可以首先通過 class.getClassLoader()來得到模塊對應的類加載器,再通過Thread.currentThread().setContextClassLoader()來設置當前線程的上下文類加載器。

提交申請后,顧問老師會電話與您溝通安排學習

免費課程推薦 >>
技術文檔推薦 >>
主站蜘蛛池模板: 国产亚洲综合色就色 | www.中文字幕在线观看 | 国产精品久久久久国产精品 | 久久久精品中文字幕 | 99在线观看免费视频 | 精品视自拍视频在线观看 | 日日干视频 | 日韩欧美精品在线视频 | 色偷偷成人网免费视频男人的天堂 | 九九九视频 | 国自产拍在线天天更新2019 | 久青草视频在线播放 | 久久99热只有频精品6不卡 | 日韩欧美一区二区三区在线 | 亚洲伊人精品综合在合线 | 不卡视频在线播放 | 欧美日韩综合精品一区二区三区 | 9久久免费国产精品特黄 | 亚洲激情视频在线播放 | 国产一区二区三区视频在线观看 | 久久久国产一区二区三区 | 第一福利在线观看 | 国产麻豆之光e奶女教师 | 强制高潮18xxxxhd日韩 | 99热在线免费 | 国产免费人成在线视频视频 | 国产亚洲欧美在线 | 久久91综合国产91久久精品 | 亚洲短视频在线观看 | 最新欧美一级视频 | 久久久久久九九 | 色婷婷久久免费网站 | 成年人视频黄色 | 欧美白人猛性xxxxx交69 | 国产精品亚洲欧美一级久久精品 | 欧美综合一区二区三区 | 色偷偷女人的天堂a在线 | 曰本人一级毛片免费完整视频 | 日本不卡在线视频高清免费 | 国内亚州视频在线观看 | 俄罗斯色视频 |